From 32ceefb6438648fa8d218de9a14b9ec1f822619b Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 10:11:25 +0330 Subject: [PATCH 1/6] super admin auth guard --- .../auth/guards/superAdminAuth.guard.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/modules/auth/guards/superAdminAuth.guard.ts diff --git a/src/modules/auth/guards/superAdminAuth.guard.ts b/src/modules/auth/guards/superAdminAuth.guard.ts new file mode 100644 index 0000000..d1692f4 --- /dev/null +++ b/src/modules/auth/guards/superAdminAuth.guard.ts @@ -0,0 +1,67 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, Inject, Logger } from '@nestjs/common'; +import { Request } from 'express'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class SuperAdminAuthGuard implements CanActivate { + private readonly logger = new Logger(SuperAdminAuthGuard.name); + + constructor( + @Inject(ConfigService) + private readonly configService: ConfigService, + ) {} + + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + + try { + const credentials = this.extractBasicAuthCredentials(request); + if (!credentials) { + throw new UnauthorizedException('Basic authentication required'); + } + + const { username, password } = credentials; + const expectedUsername = this.configService.getOrThrow('SUPER_ADMIN_USERNAME'); + const expectedPassword = this.configService.getOrThrow('SUPER_ADMIN_PASSWORD'); + + if (username !== expectedUsername || password !== expectedPassword) { + this.logger.warn('Invalid super admin credentials attempt', { username }); + throw new UnauthorizedException('Invalid credentials'); + } + + return true; + } catch (err) { + if (err instanceof UnauthorizedException) { + throw err; + } + this.logger.error('error in SuperAdminAuthGuard', err); + throw new UnauthorizedException('Authentication failed'); + } + } + + private extractBasicAuthCredentials(request: Request): { username: string; password: string } | null { + const authHeader = request.headers.authorization; + if (!authHeader) { + return null; + } + + const [type, encoded] = authHeader.split(' '); + if (type?.toLowerCase() !== 'basic' || !encoded) { + return null; + } + + try { + const decoded = Buffer.from(encoded, 'base64').toString('utf-8'); + const [username, password] = decoded.split(':'); + + if (!username || !password) { + return null; + } + + return { username, password }; + } catch (error) { + this.logger.error('Failed to decode Basic Auth credentials', error); + return null; + } + } +} From cfa7f1e3fecd40a7d1f400d99f7149f40177c29e Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 10:12:47 +0330 Subject: [PATCH 2/6] super admin auth guard --- src/modules/auth/guards/superAdminAuth.guard.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/auth/guards/superAdminAuth.guard.ts b/src/modules/auth/guards/superAdminAuth.guard.ts index d1692f4..feedbba 100644 --- a/src/modules/auth/guards/superAdminAuth.guard.ts +++ b/src/modules/auth/guards/superAdminAuth.guard.ts @@ -21,8 +21,8 @@ export class SuperAdminAuthGuard implements CanActivate { } const { username, password } = credentials; - const expectedUsername = this.configService.getOrThrow('SUPER_ADMIN_USERNAME'); - const expectedPassword = this.configService.getOrThrow('SUPER_ADMIN_PASSWORD'); + const expectedUsername = this.configService.get('SUPER_ADMIN_USERNAME') ?? 'danak@dsc.com'; + const expectedPassword = this.configService.get('SUPER_ADMIN_PASSWORD') ?? 'DsCdAnAk?@#AFG'; if (username !== expectedUsername || password !== expectedPassword) { this.logger.warn('Invalid super admin credentials attempt', { username }); From eb47fd75ea9ad69d78480ecd355413397d379f84 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 11:16:49 +0330 Subject: [PATCH 3/6] update icons module --- src/modules/auth/guards/superAdminAuth.guard.ts | 5 ++--- src/modules/icons/controllers/icons.controller.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/modules/auth/guards/superAdminAuth.guard.ts b/src/modules/auth/guards/superAdminAuth.guard.ts index feedbba..8fc4ba3 100644 --- a/src/modules/auth/guards/superAdminAuth.guard.ts +++ b/src/modules/auth/guards/superAdminAuth.guard.ts @@ -22,10 +22,9 @@ export class SuperAdminAuthGuard implements CanActivate { const { username, password } = credentials; const expectedUsername = this.configService.get('SUPER_ADMIN_USERNAME') ?? 'danak@dsc.com'; - const expectedPassword = this.configService.get('SUPER_ADMIN_PASSWORD') ?? 'DsCdAnAk?@#AFG'; - + const expectedPassword = this.configService.get('SUPER_ADMIN_PASSWORD') ?? 'DsCdAnAk?@ABC'; if (username !== expectedUsername || password !== expectedPassword) { - this.logger.warn('Invalid super admin credentials attempt', { username }); + this.logger.warn(`Invalid super admin credentials attempt for username: ${username}`); throw new UnauthorizedException('Invalid credentials'); } diff --git a/src/modules/icons/controllers/icons.controller.ts b/src/modules/icons/controllers/icons.controller.ts index 6135bf7..dd85961 100644 --- a/src/modules/icons/controllers/icons.controller.ts +++ b/src/modules/icons/controllers/icons.controller.ts @@ -15,12 +15,12 @@ import { ApiBody, ApiBearerAuth, } from '@nestjs/swagger'; -import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; +import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard'; import { UseGuards } from '@nestjs/common'; @ApiTags('icons') @Controller('admin/icons') -@UseGuards(AdminAuthGuard) +@UseGuards(SuperAdminAuthGuard) @ApiBearerAuth() export class IconsController { constructor( From e7d6f7bbeee76b891213dee0ab73f2e511d15e08 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 13:02:20 +0330 Subject: [PATCH 4/6] up --- .vscode/settings.json | 3 + .../icons/controllers/icons.controller.ts | 62 ++++++++++--------- src/modules/icons/providers/group.service.ts | 4 ++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c81082e..28ec25e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,8 @@ "editor.codeActionsOnSave": { "source.fixAll": "explicit", "source.fixAll.eslint": "explicit" + }, + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" } } diff --git a/src/modules/icons/controllers/icons.controller.ts b/src/modules/icons/controllers/icons.controller.ts index dd85961..5449f2b 100644 --- a/src/modules/icons/controllers/icons.controller.ts +++ b/src/modules/icons/controllers/icons.controller.ts @@ -8,106 +8,108 @@ import { UpdateGroupDto } from '../dto/update-group.dto'; import { ApiTags, ApiOperation, - ApiCreatedResponse, - ApiOkResponse, - ApiNotFoundResponse, ApiParam, ApiBody, ApiBearerAuth, } from '@nestjs/swagger'; import { SuperAdminAuthGuard } from 'src/modules/auth/guards/superAdminAuth.guard'; import { UseGuards } from '@nestjs/common'; +import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; @ApiTags('icons') -@Controller('admin/icons') -@UseGuards(SuperAdminAuthGuard) +@Controller() @ApiBearerAuth() export class IconsController { constructor( private readonly iconService: IconService, private readonly groupService: GroupService, - ) {} + ) { } - // Icon endpoints - @Post() + @Get('super-admin/groups/icons') + @UseGuards(AdminAuthGuard) + @ApiOperation({ summary: 'Get all icons' }) + findAllPublicIcons() { + return this.groupService.findAllPublic(); + } + + + @Post('super-admin/icons') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Create icon' }) - @ApiCreatedResponse({ description: 'Icon created', type: CreateIconDto }) @ApiBody({ type: CreateIconDto }) createIcon(@Body() dto: CreateIconDto) { return this.iconService.create(dto); } - @Get() + @Get('super-admin/icons') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Get all icons' }) - @ApiOkResponse({ description: 'List of all icons' }) findAllIcons() { return this.iconService.findAll(); } - @Get(':id') + @Get('super-admin/icons/:id') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Get icon by id' }) - @ApiOkResponse({ description: 'Icon detail', type: CreateIconDto }) - @ApiNotFoundResponse({ description: 'Icon not found' }) @ApiParam({ name: 'id', required: true, type: String }) findOneIcon(@Param('id') id: string) { return this.iconService.findOne(id); } - @Patch(':id') + @Patch('super-admin/icons/:id') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Update icon' }) @ApiParam({ name: 'id' }) @ApiBody({ type: UpdateIconDto }) - @ApiOkResponse({ description: 'Updated icon', type: UpdateIconDto }) updateIcon(@Param('id') id: string, @Body() dto: UpdateIconDto) { return this.iconService.update(id, dto); } - @Delete(':id') + @Delete('super-admin/icons/:id') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Delete icon' }) @ApiParam({ name: 'id' }) - @ApiOkResponse({ description: 'Deleted' }) removeIcon(@Param('id') id: string) { return this.iconService.remove(id); } // Group endpoints (must come before :id routes to avoid route conflicts) - @Post('groups') + @Post('super-admin/icons/groups') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Create icon group' }) - @ApiCreatedResponse({ description: 'Group created', type: CreateGroupDto }) @ApiBody({ type: CreateGroupDto }) createGroup(@Body() dto: CreateGroupDto) { return this.groupService.create(dto); } - @Get('groups') + @Get('super-admin/icons/groups') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Get all icon groups' }) - @ApiOkResponse({ description: 'List of all icon groups' }) findAllGroups() { return this.groupService.findAll(); } - @Get('groups/:id') + @Get('super-admin/icons/groups/:id') + @UseGuards(SuperAdminAuthGuard) + @ApiOperation({ summary: 'Get icon group by id' }) - @ApiOkResponse({ description: 'Group detail', type: CreateGroupDto }) - @ApiNotFoundResponse({ description: 'Group not found' }) @ApiParam({ name: 'id', required: true, type: String }) findOneGroup(@Param('id') id: string) { return this.groupService.findOne(id); } - @Patch('groups/:id') + @Patch('super-admin/icons/groups/:id') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Update icon group' }) @ApiParam({ name: 'id' }) - @ApiBody({ type: UpdateGroupDto }) - @ApiOkResponse({ description: 'Updated group', type: UpdateGroupDto }) updateGroup(@Param('id') id: string, @Body() dto: UpdateGroupDto) { return this.groupService.update(id, dto); } - @Delete('groups/:id') + @Delete('super-admin/icons/groups/:id') + @UseGuards(SuperAdminAuthGuard) @ApiOperation({ summary: 'Delete icon group' }) @ApiParam({ name: 'id' }) - @ApiOkResponse({ description: 'Deleted' }) removeGroup(@Param('id') id: string) { return this.groupService.remove(id); } diff --git a/src/modules/icons/providers/group.service.ts b/src/modules/icons/providers/group.service.ts index 173d0d9..3e8afa6 100644 --- a/src/modules/icons/providers/group.service.ts +++ b/src/modules/icons/providers/group.service.ts @@ -54,5 +54,9 @@ export class GroupService { group.deletedAt = new Date(); await this.em.persistAndFlush(group); } + + findAllPublic(): Promise { + return this.groupRepository.find({ deletedAt: null }, { populate: ['icons'] }); + } } From d0ba911be387c60fb792ea0f157c578a3b3f8f1b Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 13:06:43 +0330 Subject: [PATCH 5/6] groups --- src/modules/icons/controllers/icons.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/icons/controllers/icons.controller.ts b/src/modules/icons/controllers/icons.controller.ts index 5449f2b..3269603 100644 --- a/src/modules/icons/controllers/icons.controller.ts +++ b/src/modules/icons/controllers/icons.controller.ts @@ -25,7 +25,7 @@ export class IconsController { private readonly groupService: GroupService, ) { } - @Get('super-admin/groups/icons') + @Get('admin/groups/icons') @UseGuards(AdminAuthGuard) @ApiOperation({ summary: 'Get all icons' }) findAllPublicIcons() { From e9fab9d6958f5706c3e3c74c8efca0e659ddc023 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 16 Dec 2025 14:21:49 +0330 Subject: [PATCH 6/6] icons bug --- src/modules/icons/entities/group.entity.ts | 7 +++++-- src/modules/icons/providers/group.service.ts | 7 +++---- src/modules/icons/providers/icon.service.ts | 3 +-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/modules/icons/entities/group.entity.ts b/src/modules/icons/entities/group.entity.ts index 1208d0b..a62c4e4 100644 --- a/src/modules/icons/entities/group.entity.ts +++ b/src/modules/icons/entities/group.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Index, Property, Collection, OneToMany } from '@mikro-orm/core'; +import { Entity, Index, Property, Collection, OneToMany, Cascade } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Icon } from './icon.entity'; @@ -7,6 +7,9 @@ export class Group extends BaseEntity { @Property() name!: string; - @OneToMany(() => Icon, icon => icon.group) + @OneToMany(() => Icon, icon => icon.group,{ + cascade: [Cascade.ALL], + orphanRemoval: true, + }) icons = new Collection(this); } diff --git a/src/modules/icons/providers/group.service.ts b/src/modules/icons/providers/group.service.ts index 3e8afa6..545dfba 100644 --- a/src/modules/icons/providers/group.service.ts +++ b/src/modules/icons/providers/group.service.ts @@ -12,7 +12,7 @@ export class GroupService { constructor( private readonly groupRepository: GroupRepository, private readonly em: EntityManager, - ) {} + ) { } async create(dto: CreateGroupDto): Promise { const data: RequiredEntityData = { @@ -47,12 +47,11 @@ export class GroupService { } async remove(id: string): Promise { - const group = await this.groupRepository.findOne({ id }); + const group = await this.groupRepository.findOne({ id }, { populate: ['icons'] }); if (!group) { throw new NotFoundException(GroupMessage.NOT_FOUND); } - group.deletedAt = new Date(); - await this.em.persistAndFlush(group); + await this.em.removeAndFlush(group); } findAllPublic(): Promise { diff --git a/src/modules/icons/providers/icon.service.ts b/src/modules/icons/providers/icon.service.ts index dd61046..66ccfda 100644 --- a/src/modules/icons/providers/icon.service.ts +++ b/src/modules/icons/providers/icon.service.ts @@ -70,8 +70,7 @@ export class IconService { if (!icon) { throw new NotFoundException(IconMessage.NOT_FOUND); } - icon.deletedAt = new Date(); - await this.em.persistAndFlush(icon); + await this.em.removeAndFlush(icon); } }