diff --git a/src/Types&Enums/role.enum.ts b/src/Types&Enums/role.enum.ts index 9b6439b..c71b816 100644 --- a/src/Types&Enums/role.enum.ts +++ b/src/Types&Enums/role.enum.ts @@ -8,4 +8,5 @@ export enum RoleEnum { USER = "user", FILE_MAKER = "file_maker", FILE_REVIEWER = "file_reviewer", + SUPER_ADMIN = "super_admin", } diff --git a/src/app.module.ts b/src/app.module.ts index 0652b14..3d42844 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -27,6 +27,7 @@ import { CronModule } from "./utils/cron/cron.module"; import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module"; import { DatabaseModule } from "./core/database/database.module"; import { AppConfigModule } from "./core/config/config.module"; +import { SuperAdminModule } from "./super-admin/super-admin.module"; @Module({ imports: [ @@ -59,6 +60,7 @@ import { AppConfigModule } from "./core/config/config.module"; ExpertInsurerModule, LookupsModule, WorkflowStepManagementModule, + SuperAdminModule, ], controllers: [], providers: [ diff --git a/src/auth/auth-controllers/actor/actor.auth.controller.ts b/src/auth/auth-controllers/actor/actor.auth.controller.ts index 10208ac..3cbd777 100644 --- a/src/auth/auth-controllers/actor/actor.auth.controller.ts +++ b/src/auth/auth-controllers/actor/actor.auth.controller.ts @@ -37,6 +37,7 @@ import { LegalRegisterDto, } from "src/auth/dto/actor/register.actor.dto"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; +import { SuperAdminGuard } from "src/super-admin/guards/super-admin.guard"; import { Roles } from "src/decorators/roles.decorator"; import { CurrentUser } from "src/decorators/user.decorator"; @@ -95,6 +96,7 @@ export class ActorAuthController { * will be removed in a future release. */ @Post("register/genuine") + @UseGuards(SuperAdminGuard) @ApiOperation({ deprecated: true, summary: "[DEPRECATED] Genuine actor registration", @@ -111,6 +113,7 @@ export class ActorAuthController { * will be removed in a future release. */ @Post("register/legal") + @UseGuards(SuperAdminGuard) @ApiOperation({ deprecated: true, summary: "[DEPRECATED] Legal actor registration", @@ -123,21 +126,24 @@ export class ActorAuthController { } @Post("register/insurer") + @UseGuards(SuperAdminGuard) @ApiBody({ type: InsurerRegisterDto }) async registerInsurer(@Body() body: InsurerRegisterDto) { return await this.actorAuthService.insurerRegister(body); } - /** Mock: create a field expert for testing. Make private later. */ + /** Requires super-admin token. */ @Post("create-field-expert") + @UseGuards(SuperAdminGuard) @ApiBody({ type: CreateFieldExpertDto }) @ApiAcceptedResponse() async createFieldExpert(@Body() body: CreateFieldExpertDto) { return await this.actorAuthService.createFieldExpertMock(body); } - /** Mock: create a registrar for testing. Make private later. */ + /** Requires super-admin token. */ @Post("create-registrar") + @UseGuards(SuperAdminGuard) @ApiBody({ type: CreateRegistrarDto }) @ApiAcceptedResponse() async createRegistrar(@Body() body: CreateRegistrarDto) { diff --git a/src/auth/auth-services/actor.auth.service.ts b/src/auth/auth-services/actor.auth.service.ts index 185950c..3d87a8c 100644 --- a/src/auth/auth-services/actor.auth.service.ts +++ b/src/auth/auth-services/actor.auth.service.ts @@ -31,6 +31,7 @@ import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db. import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service"; import { HashService } from "src/utils/hash/hash.service"; import { OtpGeneratorService } from "src/sms-orchestration/otp-generator.service"; +import { SuperAdminDbService } from "src/super-admin/entities/db-service/super-admin.db.service"; function pick(obj: Record, keys: string[]) { const out: Record = {}; @@ -56,6 +57,7 @@ export class ActorAuthService { private readonly captchaChallengeService: CaptchaChallengeService, private readonly fileMakerDbService: FileMakerDbService, private readonly fileReviewerDbService: FileReviewerDbService, + private readonly superAdminDbService: SuperAdminDbService, ) {} // TODO convrt to class for dynamic controller @@ -114,6 +116,13 @@ export class ActorAuthService { res = await this.fileReviewerDbService.findByLoginIdentifier(username); break; + case RoleEnum.SUPER_ADMIN: + if (username == null && userId) + res = await this.superAdminDbService.findOne({ + _id: new Types.ObjectId(userId), + }); + else res = await this.superAdminDbService.findByLoginIdentifier(username); + break; default: return null; } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 5a5de8b..ab512e9 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -29,6 +29,11 @@ import { UsersModule } from "src/users/users.module"; import { HashModule } from "src/utils/hash/hash.module"; import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module"; import { CaptchaModule } from "src/captcha/captcha.module"; +import { SuperAdminDbService } from "src/super-admin/entities/db-service/super-admin.db.service"; +import { + SuperAdminModel, + SuperAdminSchema, +} from "src/super-admin/entities/schema/super-admin.schema"; /** Auth services and guards are app-wide (avoids importing AuthModule in every feature module). */ @Global() @@ -47,6 +52,7 @@ import { CaptchaModule } from "src/captcha/captcha.module"; schema: ClaimRequestManagementSchema, }, { name: ClaimCase.name, schema: ClaimCaseSchema }, + { name: SuperAdminModel.name, schema: SuperAdminSchema }, ]), JwtModule.register({ signOptions: { expiresIn: "1h" }, // TODO: MAKE IT ENV @@ -61,6 +67,7 @@ import { CaptchaModule } from "src/captcha/captcha.module"; JwtService, LocalActorAuthGuard, LocalUserAuthGuard, + SuperAdminDbService, ], exports: [ UserAuthService, @@ -68,6 +75,7 @@ import { CaptchaModule } from "src/captcha/captcha.module"; JwtService, LocalActorAuthGuard, LocalUserAuthGuard, + SuperAdminDbService, ], controllers: [UserAuthController, ActorAuthController], }) diff --git a/src/auth/guards/actor-local.guard.ts b/src/auth/guards/actor-local.guard.ts index 9c8dc2d..a130590 100644 --- a/src/auth/guards/actor-local.guard.ts +++ b/src/auth/guards/actor-local.guard.ts @@ -45,6 +45,7 @@ export class LocalActorAuthGuard implements CanActivate { RoleEnum.REGISTRAR, RoleEnum.FILE_MAKER, RoleEnum.FILE_REVIEWER, + RoleEnum.SUPER_ADMIN, ].includes(payload.role) ) { throw new UnauthorizedException("User role is not authorized"); diff --git a/src/client/client.controller.ts b/src/client/client.controller.ts index 13ff777..a11b4eb 100644 --- a/src/client/client.controller.ts +++ b/src/client/client.controller.ts @@ -1,11 +1,20 @@ -import { Body, Controller, Get, Patch, Post } from "@nestjs/common"; import { + Body, + Controller, + Get, + Patch, + Post, + UseGuards, +} from "@nestjs/common"; +import { + ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags, } from "@nestjs/swagger"; import { CurrentUser } from "src/decorators/user.decorator"; +import { SuperAdminGuard } from "src/super-admin/guards/super-admin.guard"; import { SystemSettingsResponseDto } from "src/system-settings/dto/system-settings.dto"; import { SystemSettingsService } from "src/system-settings/system-settings.service"; import { ClientService } from "./client.service"; @@ -21,26 +30,35 @@ export class ClientController { ) {} @Post() + @UseGuards(SuperAdminGuard) + @ApiBearerAuth() + @ApiOperation({ summary: "Create a new insurer client (super-admin only)" }) async addClient(@Body() client: ClientDto) { return await this.clientService.addClient(client); } @Get() + @UseGuards(SuperAdminGuard) + @ApiBearerAuth() async getClient(@CurrentUser() user) { return await this.clientService.getClients(); } @Get("list") + @UseGuards(SuperAdminGuard) + @ApiBearerAuth() async getClientList(@CurrentUser() user) { return await this.clientService.getClientList(); } /** Toggle SandHub/Tejarat live HTTP vs mock inquiries (`system_settings.externalApis.sandHubUseLiveApi`). */ @Patch("external-inquiries-live") + @UseGuards(SuperAdminGuard) + @ApiBearerAuth() @ApiOperation({ - summary: "Enable or disable live external inquiries", + summary: "Enable or disable live external inquiries (super-admin only)", description: - "Updates `system_settings.externalApis.sandHubUseLiveApi`. No auth required. Use the request examples below to switch between live Tejarat/SandHub HTTP and offline mock mode.", + "Updates `system_settings.externalApis.sandHubUseLiveApi`. Use the request examples below to switch between live Tejarat/SandHub HTTP and offline mock mode.", }) @ApiBody({ type: SetExternalInquiriesLiveDto, @@ -52,7 +70,8 @@ export class ClientController { }, disableLive: { summary: "Disable live inquiries (mock mode)", - description: "Use mocked inquiry responses; flows continue without external connectivity.", + description: + "Use mocked inquiry responses; flows continue without external connectivity.", value: { enabled: false }, }, }, diff --git a/src/super-admin/dto/super-admin.dto.ts b/src/super-admin/dto/super-admin.dto.ts new file mode 100644 index 0000000..36f9ab1 --- /dev/null +++ b/src/super-admin/dto/super-admin.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsEmail, IsNotEmpty, IsOptional, IsString } from "class-validator"; + +export class CreateSuperAdminDto { + @ApiProperty({ + example: "ops@yara724.ir", + description: "Email address for the new super-admin account.", + }) + @IsEmail() + email: string; + + @ApiProperty({ example: "Str0ngP@ss!" }) + @IsString() + @IsNotEmpty() + password: string; + + @ApiPropertyOptional({ example: "Operations" }) + @IsOptional() + @IsString() + firstName?: string; + + @ApiPropertyOptional({ example: "Team" }) + @IsOptional() + @IsString() + lastName?: string; +} + +export class CreateFieldExpertAdminDto { + @ApiProperty({ example: "expert@insurer.ir" }) + @IsEmail() + email: string; + + @ApiProperty({ example: "Expert@724" }) + @IsString() + @IsNotEmpty() + password: string; + + @ApiProperty({ example: "Ali" }) + @IsString() + @IsNotEmpty() + firstName: string; + + @ApiProperty({ example: "Mohammadi" }) + @IsString() + @IsNotEmpty() + lastName: string; + + @ApiProperty({ example: "09121234567" }) + @IsString() + @IsNotEmpty() + mobile: string; + + @ApiPropertyOptional({ example: "02112345678" }) + @IsOptional() + @IsString() + phone?: string; +} + +export class CreateRegistrarAdminDto { + @ApiProperty({ example: "registrar@insurer.ir" }) + @IsEmail() + email: string; + + @ApiProperty({ example: "Registrar@724" }) + @IsString() + @IsNotEmpty() + password: string; + + @ApiProperty({ + example: "664a1b2c3d4e5f6789012345", + description: "Mongo ObjectId of the insurer client this registrar belongs to.", + }) + @IsString() + @IsNotEmpty() + clientId: string; +} diff --git a/src/super-admin/entities/db-service/super-admin.db.service.ts b/src/super-admin/entities/db-service/super-admin.db.service.ts new file mode 100644 index 0000000..23c61f1 --- /dev/null +++ b/src/super-admin/entities/db-service/super-admin.db.service.ts @@ -0,0 +1,74 @@ +import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { FilterQuery, Model } from "mongoose"; +import { HashService } from "src/utils/hash/hash.service"; +import { + SuperAdminModel, + SuperAdminDocument, +} from "../schema/super-admin.schema"; +import { RoleEnum } from "src/Types&Enums/role.enum"; + +@Injectable() +export class SuperAdminDbService implements OnModuleInit { + private readonly logger = new Logger(SuperAdminDbService.name); + + constructor( + @InjectModel(SuperAdminModel.name) + private readonly superAdminModel: Model, + private readonly hashService: HashService, + ) {} + + /** + * Seeds the default super-admin account on first boot. + * Reads credentials from environment variables: + * SUPER_ADMIN_EMAIL (default: super-admin@yara724.ir) + * SUPER_ADMIN_PASSWORD (default: SuperAdmin@724) + */ + async onModuleInit() { + const email = ( + process.env.SUPER_ADMIN_EMAIL ?? "super-admin@yara724.ir" + ).toLowerCase().trim(); + + const existing = await this.superAdminModel.findOne({ email }).lean(); + if (existing) { + this.logger.log(`Super-admin already exists (email=${email}), skipping seed.`); + return; + } + + const rawPassword = process.env.SUPER_ADMIN_PASSWORD ?? "SuperAdmin@724"; + const password = await this.hashService.hash(rawPassword); + + await this.superAdminModel.create({ + email, + password, + role: RoleEnum.SUPER_ADMIN, + firstName: "Super", + lastName: "Admin", + }); + + this.logger.log(`Default super-admin seeded (email=${email}).`); + } + + async findOne( + filter: FilterQuery, + ): Promise { + return this.superAdminModel.findOne(filter).lean() as any; + } + + async findByLoginIdentifier(identifier: string): Promise { + const id = identifier.trim(); + return this.superAdminModel.findOne({ email: id }).lean() as any; + } + + async create(data: Partial): Promise { + return this.superAdminModel.create(data); + } + + async updateOne(filter: FilterQuery, update: any) { + return this.superAdminModel.updateOne(filter, update); + } + + async findAll(): Promise { + return this.superAdminModel.find().lean() as any; + } +} diff --git a/src/super-admin/entities/schema/super-admin.schema.ts b/src/super-admin/entities/schema/super-admin.schema.ts new file mode 100644 index 0000000..def101a --- /dev/null +++ b/src/super-admin/entities/schema/super-admin.schema.ts @@ -0,0 +1,30 @@ +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import { HydratedDocument } from "mongoose"; +import { RoleEnum } from "src/Types&Enums/role.enum"; + +@Schema({ + collection: "super-admins", + versionKey: false, + timestamps: true, +}) +export class SuperAdminModel { + @Prop({ required: true, unique: true, index: true }) + email: string; + + @Prop({ required: true }) + password: string; + + @Prop({ required: true, default: RoleEnum.SUPER_ADMIN }) + role: RoleEnum; + + @Prop() + firstName?: string; + + @Prop() + lastName?: string; + + createdAt: Date; +} + +export type SuperAdminDocument = HydratedDocument; +export const SuperAdminSchema = SchemaFactory.createForClass(SuperAdminModel); diff --git a/src/super-admin/guards/super-admin.guard.ts b/src/super-admin/guards/super-admin.guard.ts new file mode 100644 index 0000000..47a4eb5 --- /dev/null +++ b/src/super-admin/guards/super-admin.guard.ts @@ -0,0 +1,49 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from "@nestjs/common"; +import { JwtService } from "@nestjs/jwt"; +import { Request } from "express"; +import { RoleEnum } from "src/Types&Enums/role.enum"; + +/** + * Verifies a Bearer JWT and restricts access to `super_admin` role only. + * Use this guard on all endpoints that should be reachable only by the + * super-admin panel. + */ +@Injectable() +export class SuperAdminGuard implements CanActivate { + constructor(private readonly jwtService: JwtService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) { + throw new UnauthorizedException("Token not found"); + } + + let payload: any; + try { + payload = await this.jwtService.verifyAsync(token, { + secret: `${process.env.JWT_SECRET}`, + }); + } catch { + throw new UnauthorizedException("Invalid or expired token"); + } + + if (payload?.role !== RoleEnum.SUPER_ADMIN) { + throw new UnauthorizedException("Super-admin access required"); + } + + (request as any).user = payload; + (request as any).identity = payload; + return true; + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(" ") ?? []; + return type === "Bearer" ? token : undefined; + } +} diff --git a/src/super-admin/super-admin.controller.ts b/src/super-admin/super-admin.controller.ts new file mode 100644 index 0000000..d102c56 --- /dev/null +++ b/src/super-admin/super-admin.controller.ts @@ -0,0 +1,162 @@ +import { + Body, + Controller, + Get, + Patch, + Post, + UseGuards, +} from "@nestjs/common"; +import { + ApiBearerAuth, + ApiBody, + ApiOperation, + ApiResponse, + ApiTags, +} from "@nestjs/swagger"; +import { SuperAdminGuard } from "./guards/super-admin.guard"; +import { SuperAdminService } from "./super-admin.service"; +import { + CreateSuperAdminDto, + CreateFieldExpertAdminDto, + CreateRegistrarAdminDto, +} from "./dto/super-admin.dto"; +import { ClientDto } from "src/client/dto/create-client.dto"; +import { + InsurerRegisterDto, + GenuineRegisterDto, + LegalRegisterDto, +} from "src/auth/dto/actor/register.actor.dto"; +import { SystemSettingsService } from "src/system-settings/system-settings.service"; +import { + SystemSettingsResponseDto, + UpdateSystemSettingsDto, +} from "src/system-settings/dto/system-settings.dto"; +import { SetExternalInquiriesLiveDto } from "src/client/dto/external-inquiries-live.dto"; + +@ApiTags("super-admin") +@ApiBearerAuth() +@UseGuards(SuperAdminGuard) +@Controller("super-admin") +export class SuperAdminController { + constructor( + private readonly superAdminService: SuperAdminService, + private readonly systemSettingsService: SystemSettingsService, + ) {} + + // ── Super-admin account management ─────────────────────────────────────── + + @Get("accounts") + @ApiOperation({ summary: "List all super-admin accounts" }) + listSuperAdmins() { + return this.superAdminService.listSuperAdmins(); + } + + @Post("accounts") + @ApiOperation({ summary: "Create an additional super-admin account" }) + @ApiBody({ type: CreateSuperAdminDto }) + createSuperAdmin(@Body() body: CreateSuperAdminDto) { + return this.superAdminService.createSuperAdmin(body); + } + + // ── Client / insurer management ────────────────────────────────────────── + + @Post("clients") + @ApiOperation({ + summary: "Create a new insurer client", + description: "Registers a new insurer (client) in the platform.", + }) + @ApiBody({ type: ClientDto }) + createClient(@Body() body: ClientDto) { + return this.superAdminService.createClient(body); + } + + @Get("clients") + @ApiOperation({ summary: "List all insurer clients" }) + listClients() { + return this.superAdminService.listClients(); + } + + @Get("clients/names") + @ApiOperation({ summary: "List insurer client names and IDs" }) + listClientNames() { + return this.superAdminService.listClientNames(); + } + + // ── Actor registration ─────────────────────────────────────────────────── + + @Post("register/insurer") + @ApiOperation({ summary: "Register a new insurer (company) actor" }) + @ApiBody({ type: InsurerRegisterDto }) + registerInsurer(@Body() body: InsurerRegisterDto) { + return this.superAdminService.registerInsurer(body); + } + + @Post("register/genuine") + @ApiOperation({ + deprecated: true, + summary: "[DEPRECATED] Register a genuine actor", + description: + "Kept for legacy clients. Use the unified onboarding flow instead.", + }) + @ApiBody({ type: GenuineRegisterDto }) + registerGenuine(@Body() body: GenuineRegisterDto) { + return this.superAdminService.registerGenuine(body); + } + + @Post("register/legal") + @ApiOperation({ + deprecated: true, + summary: "[DEPRECATED] Register a legal actor", + description: + "Kept for legacy clients. Use the unified onboarding flow instead.", + }) + @ApiBody({ type: LegalRegisterDto }) + registerLegal(@Body() body: LegalRegisterDto) { + return this.superAdminService.registerLegal(body); + } + + @Post("create-field-expert") + @ApiOperation({ summary: "Create a field expert" }) + @ApiBody({ type: CreateFieldExpertAdminDto }) + createFieldExpert(@Body() body: CreateFieldExpertAdminDto) { + return this.superAdminService.createFieldExpert(body); + } + + @Post("create-registrar") + @ApiOperation({ summary: "Create a registrar" }) + @ApiBody({ type: CreateRegistrarAdminDto }) + createRegistrar(@Body() body: CreateRegistrarAdminDto) { + return this.superAdminService.createRegistrar(body); + } + + // ── System settings ────────────────────────────────────────────────────── + + @Get("system-settings") + @ApiOperation({ summary: "Get global system settings" }) + @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) + getSystemSettings() { + return this.systemSettingsService.getSettingsView(); + } + + @Patch("system-settings") + @ApiOperation({ summary: "Update global system settings" }) + @ApiBody({ type: UpdateSystemSettingsDto }) + @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) + updateSystemSettings(@Body() body: UpdateSystemSettingsDto) { + return this.systemSettingsService.updateSettings(body); + } + + @Patch("external-inquiries-live") + @ApiOperation({ + summary: "Enable or disable live external inquiries globally", + description: + "Updates `system_settings.externalApis.sandHubUseLiveApi`. When disabled, all inquiry flows use mocks.", + }) + @ApiBody({ type: SetExternalInquiriesLiveDto }) + @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) + setExternalInquiriesLive(@Body() body: SetExternalInquiriesLiveDto) { + return this.systemSettingsService.updateSettings({ + externalApis: { sandHubUseLiveApi: body?.enabled === true }, + }); + } +} diff --git a/src/super-admin/super-admin.module.ts b/src/super-admin/super-admin.module.ts new file mode 100644 index 0000000..e175127 --- /dev/null +++ b/src/super-admin/super-admin.module.ts @@ -0,0 +1,34 @@ +import { Module } from "@nestjs/common"; +import { ClientModule } from "src/client/client.module"; +import { SystemSettingsModule } from "src/system-settings/system-settings.module"; +import { HashModule } from "src/utils/hash/hash.module"; +import { UsersModule } from "src/users/users.module"; +import { SuperAdminController } from "./super-admin.controller"; +import { SuperAdminService } from "./super-admin.service"; +import { SuperAdminGuard } from "./guards/super-admin.guard"; + +/** + * Super-admin feature module. + * + * The `SuperAdminDbService` and its Mongoose model are registered in `AuthModule` + * (which is `@Global()`) to keep login routing central. This module only needs + * to import the other feature modules it proxies. + * + * Dependency chain: + * SuperAdminGuard ← JwtService (global via AuthModule JwtModule) + * SuperAdminService ← ActorAuthService (global), ClientService (ClientModule), + * SuperAdminDbService (global via AuthModule), + * FieldExpertDbService + RegistrarDbService (UsersModule) + */ +@Module({ + imports: [ + ClientModule, + SystemSettingsModule, + HashModule, + UsersModule, + ], + controllers: [SuperAdminController], + providers: [SuperAdminService, SuperAdminGuard], + exports: [SuperAdminService, SuperAdminGuard], +}) +export class SuperAdminModule {} diff --git a/src/super-admin/super-admin.service.ts b/src/super-admin/super-admin.service.ts new file mode 100644 index 0000000..5a91545 --- /dev/null +++ b/src/super-admin/super-admin.service.ts @@ -0,0 +1,102 @@ +import { + BadRequestException, + ConflictException, + Injectable, + NotFoundException, +} from "@nestjs/common"; +import { Types } from "mongoose"; +import { HashService } from "src/utils/hash/hash.service"; +import { ClientService } from "src/client/client.service"; +import { ClientDto } from "src/client/dto/create-client.dto"; +import { ActorAuthService } from "src/auth/auth-services/actor.auth.service"; +import { + InsurerRegisterDto, + GenuineRegisterDto, + LegalRegisterDto, +} from "src/auth/dto/actor/register.actor.dto"; +import { SuperAdminDbService } from "./entities/db-service/super-admin.db.service"; +import { + CreateSuperAdminDto, + CreateFieldExpertAdminDto, + CreateRegistrarAdminDto, +} from "./dto/super-admin.dto"; +import { RoleEnum } from "src/Types&Enums/role.enum"; +import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service"; +import { RegistrarDbService } from "src/users/entities/db-service/registrar.db.service"; + +@Injectable() +export class SuperAdminService { + constructor( + private readonly superAdminDbService: SuperAdminDbService, + private readonly hashService: HashService, + private readonly clientService: ClientService, + private readonly actorAuthService: ActorAuthService, + private readonly fieldExpertDbService: FieldExpertDbService, + private readonly registrarDbService: RegistrarDbService, + ) {} + + /** List all super-admin accounts (sans password). */ + async listSuperAdmins() { + const admins = await this.superAdminDbService.findAll(); + return admins.map(({ password: _pw, ...rest }) => rest); + } + + /** Create an additional super-admin account. */ + async createSuperAdmin(body: CreateSuperAdminDto) { + const email = body.email.toLowerCase().trim(); + const existing = await this.superAdminDbService.findOne({ email }); + if (existing) { + throw new ConflictException( + "A super-admin with this email already exists.", + ); + } + const password = await this.hashService.hash(body.password); + const created = await this.superAdminDbService.create({ + email, + password, + role: RoleEnum.SUPER_ADMIN, + firstName: body.firstName, + lastName: body.lastName, + }); + const { password: _pw, ...rest } = (created as any).toObject + ? (created as any).toObject() + : (created as any); + return rest; + } + + // ── Client management ──────────────────────────────────────────────────── + + async createClient(body: ClientDto) { + return this.clientService.addClient(body); + } + + async listClients() { + return this.clientService.getClients(); + } + + async listClientNames() { + return this.clientService.getClientList(); + } + + // ── Actor registration proxies ─────────────────────────────────────────── + + async registerInsurer(body: InsurerRegisterDto) { + return this.actorAuthService.insurerRegister(body); + } + + async registerGenuine(body: GenuineRegisterDto) { + return this.actorAuthService.genuineRegister(body); + } + + async registerLegal(body: LegalRegisterDto) { + return this.actorAuthService.legalRegister(body); + } + + async createFieldExpert(body: CreateFieldExpertAdminDto) { + return this.actorAuthService.createFieldExpertMock(body); + } + + async createRegistrar(body: CreateRegistrarAdminDto) { + return this.actorAuthService.createRegistrarMock(body); + } +} diff --git a/src/system-settings/system-settings.controller.ts b/src/system-settings/system-settings.controller.ts index ff346be..5915851 100644 --- a/src/system-settings/system-settings.controller.ts +++ b/src/system-settings/system-settings.controller.ts @@ -23,7 +23,7 @@ export class SystemSettingsController { @Get() @UseGuards(SettingsJwtGuard, RolesGuard) - @Roles(RoleEnum.ADMIN, RoleEnum.COMPANY) + @Roles(RoleEnum.ADMIN, RoleEnum.COMPANY, RoleEnum.SUPER_ADMIN) @ApiOperation({ summary: "Get global system settings (external API toggles)", description: @@ -36,7 +36,7 @@ export class SystemSettingsController { @Patch() @UseGuards(SettingsJwtGuard, RolesGuard) - @Roles(RoleEnum.ADMIN) + @Roles(RoleEnum.ADMIN, RoleEnum.SUPER_ADMIN) @ApiOperation({ summary: "Update global system settings", description: