From 43b422a81ece5978469b608fd232276df1692496 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sat, 24 Jan 2026 12:49:25 +0530 Subject: [PATCH] feat: Introduce default profile sections and fields, updating schema with `isSystem` and `isSearchableOnly` properties. --- prisma/schema.prisma | 3 + prisma/seed.ts | 130 +++++++++++++++++- src/profile-fields/dto/create-field.dto.ts | 5 + .../profile-sections.service.ts | 9 +- 4 files changed, 145 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bba762c..793218f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -224,6 +224,7 @@ model ProfileSection { sortOrder Int @default(0) isActive Boolean @default(true) isGlobal Boolean @default(false) // If true, applies to ALL agent types + isSystem Boolean @default(false) // If true, section cannot be deleted (system default) // Timestamps createdAt DateTime @default(now()) @@ -234,6 +235,7 @@ model ProfileSection { agentTypeSections AgentTypeSection[] @@index([isActive]) + @@index([isSystem]) @@index([sortOrder]) @@map("profile_sections") } @@ -273,6 +275,7 @@ model ProfileField { sortOrder Int @default(0) isActive Boolean @default(true) isRequired Boolean @default(false) + isSearchableOnly Boolean @default(false) // If true, shown in edit form & search, but hidden on public profile // Validation rules (JSON) validation Json? // { min, max, minLength, maxLength, pattern, etc. } diff --git a/prisma/seed.ts b/prisma/seed.ts index 2b67ccb..e7b1051 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,4 +1,4 @@ -import { PrismaClient, UserRole, UserStatus, AuthProvider } from '@prisma/client'; +import { PrismaClient, UserRole, UserStatus, AuthProvider, FieldType } from '@prisma/client'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; import * as argon2 from 'argon2'; @@ -49,6 +49,129 @@ async function main() { console.log(''); + // ============================================= + // Seed Default Profile Sections & Fields + // ============================================= + console.log('šŸ“‹ Seeding Default Profile Sections...'); + + // Define default system sections with their fields + const defaultSections = [ + { + name: 'Basic Information', + slug: 'basic-information', + description: 'Basic profile information', + icon: 'user', + sortOrder: 1, + isActive: true, + isGlobal: true, + isSystem: true, + fields: [ + { + name: 'First Name', + slug: 'first_name', + fieldType: FieldType.TEXT, + description: 'Your first name', + placeholder: 'Enter your first name', + sortOrder: 1, + isActive: true, + isRequired: true, + isSearchableOnly: false, + }, + { + name: 'Last Name', + slug: 'last_name', + fieldType: FieldType.TEXT, + description: 'Your last name', + placeholder: 'Enter your last name', + sortOrder: 2, + isActive: true, + isRequired: true, + isSearchableOnly: false, + }, + ], + }, + { + name: 'Contact Information', + slug: 'contact-information', + description: 'Contact details', + icon: 'phone', + sortOrder: 2, + isActive: true, + isGlobal: true, + isSystem: true, + fields: [ + { + name: 'Email Address', + slug: 'email_address', + fieldType: FieldType.TEXT, + description: 'Your contact email address', + placeholder: 'Enter your email address', + sortOrder: 1, + isActive: true, + isRequired: true, + isSearchableOnly: false, + }, + { + name: 'Phone Number', + slug: 'phone_number', + fieldType: FieldType.TEXT, + description: 'Your contact phone number', + placeholder: 'Enter your phone number', + sortOrder: 2, + isActive: true, + isRequired: false, + isSearchableOnly: false, + }, + ], + }, + ]; + + for (const sectionData of defaultSections) { + const { fields, ...sectionInfo } = sectionData; + + // Check if section already exists + const existingSection = await prisma.profileSection.findUnique({ + where: { slug: sectionInfo.slug }, + }); + + if (existingSection) { + console.log(` āš ļø Section "${sectionInfo.name}" already exists`); + + // Still check and create missing fields + for (const fieldData of fields) { + const existingField = await prisma.profileField.findFirst({ + where: { + sectionId: existingSection.id, + slug: fieldData.slug, + }, + }); + + if (!existingField) { + await prisma.profileField.create({ + data: { + ...fieldData, + sectionId: existingSection.id, + }, + }); + console.log(` āœ… Created field: ${fieldData.name}`); + } + } + } else { + // Create section with fields + const section = await prisma.profileSection.create({ + data: { + ...sectionInfo, + fields: { + create: fields, + }, + }, + }); + console.log(` āœ… Created section: ${sectionInfo.name} with ${fields.length} fields`); + } + } + + console.log(''); + // ============================================= // Seed Admin User // ============================================= @@ -96,11 +219,16 @@ async function main() { const userCount = await prisma.user.count(); const adminCount = await prisma.user.count({ where: { role: UserRole.ADMIN } }); const agentTypeCount = await prisma.agentType.count(); + const sectionCount = await prisma.profileSection.count(); + const fieldCount = await prisma.profileField.count(); + const systemSectionCount = await prisma.profileSection.count({ where: { isSystem: true } }); console.log('šŸ“Š Database Summary:'); console.log(` Total Users: ${userCount}`); console.log(` Admins: ${adminCount}`); console.log(` Agent Types: ${agentTypeCount}`); + console.log(` Profile Sections: ${sectionCount} (${systemSectionCount} system)`); + console.log(` Profile Fields: ${fieldCount}`); console.log('\n🌱 Seeding completed!\n'); await prisma.$disconnect(); diff --git a/src/profile-fields/dto/create-field.dto.ts b/src/profile-fields/dto/create-field.dto.ts index bcd98b3..06b8e70 100644 --- a/src/profile-fields/dto/create-field.dto.ts +++ b/src/profile-fields/dto/create-field.dto.ts @@ -89,6 +89,11 @@ export class CreateFieldDto { @IsBoolean() isRequired?: boolean; + @ApiPropertyOptional({ description: 'Whether this field is for search only (hidden from public profile)', default: false }) + @IsOptional() + @IsBoolean() + isSearchableOnly?: boolean; + @ApiPropertyOptional({ description: 'Validation rules (JSON object)' }) @IsOptional() @IsObject() diff --git a/src/profile-fields/profile-sections.service.ts b/src/profile-fields/profile-sections.service.ts index 259d6ba..c773de9 100644 --- a/src/profile-fields/profile-sections.service.ts +++ b/src/profile-fields/profile-sections.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; +import { Injectable, NotFoundException, ConflictException, ForbiddenException } from '@nestjs/common'; import { PrismaService } from '../prisma'; import { CreateSectionDto, UpdateSectionDto, AssignSectionDto, UpdateAssignmentDto } from './dto'; @@ -143,6 +143,13 @@ export class ProfileSectionsService { async remove(id: string) { const section = await this.findOne(id); + // Check if this is a system section + if (section.isSystem) { + throw new ForbiddenException( + 'Cannot delete system sections. You can only deactivate them.', + ); + } + // Check if section has any fields if (section._count.fields > 0) { throw new ConflictException(