diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 4cee3fe..ddc5631 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -318,6 +318,19 @@ export class ControllerRoute { 'Fetches a list of all products along with their associated device details'; }; }; + + static TAG = class { + public static readonly ROUTE = 'tags'; + static ACTIONS = class { + public static readonly CREATE_TAG_SUMMARY = 'Create a new tag'; + public static readonly CREATE_TAG_DESCRIPTION = + 'Creates a new tag and assigns it to a specific project and product.'; + public static readonly GET_TAGS_BY_PROJECT_SUMMARY = + 'Get tags by project'; + public static readonly GET_TAGS_BY_PROJECT_DESCRIPTION = + 'Retrieves a list of tags associated with a specific project.'; + }; + }; static USER = class { public static readonly ROUTE = '/user'; diff --git a/libs/common/src/modules/tag/repositories/tag-repository.ts b/libs/common/src/modules/tag/repositories/tag-repository.ts index d82b0fb..95d1b84 100644 --- a/libs/common/src/modules/tag/repositories/tag-repository.ts +++ b/libs/common/src/modules/tag/repositories/tag-repository.ts @@ -3,7 +3,7 @@ import { NewTagEntity } from '../entities'; import { DataSource, Repository } from 'typeorm'; @Injectable() -export class NewTagRepository extends Repository { +export class NewTagRepository extends Repository { constructor(private dataSource: DataSource) { super(NewTagEntity, dataSource.createEntityManager()); } diff --git a/src/app.module.ts b/src/app.module.ts index f941837..f2a5bd8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -28,6 +28,7 @@ import { PermissionModule } from './permission/permission.module'; import { RoleModule } from './role/role.module'; import { TermsConditionsModule } from './terms-conditions/terms-conditions.module'; import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module'; +import { TagModule } from './tags/tags.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -59,6 +60,7 @@ import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module'; RoleModule, TermsConditionsModule, PrivacyPolicyModule, + TagModule, ], providers: [ { diff --git a/src/tags/controllers/index.ts b/src/tags/controllers/index.ts new file mode 100644 index 0000000..be960c9 --- /dev/null +++ b/src/tags/controllers/index.ts @@ -0,0 +1 @@ +export * from './tags.controller'; diff --git a/src/tags/controllers/tags.controller.ts b/src/tags/controllers/tags.controller.ts new file mode 100644 index 0000000..0477601 --- /dev/null +++ b/src/tags/controllers/tags.controller.ts @@ -0,0 +1,42 @@ +import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { TagService } from '../services/tags.service'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CreateTagDto } from '../dtos/tags.dto'; +import { GetTagsParam } from '../dtos/get-tags.param'; + +@ApiTags('Tag Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.TAG.ROUTE, +}) +export class TagController { + constructor(private readonly tagService: TagService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + @ApiOperation({ + summary: ControllerRoute.TAG.ACTIONS.CREATE_TAG_SUMMARY, + description: ControllerRoute.TAG.ACTIONS.CREATE_TAG_DESCRIPTION, + }) + async createTag(@Body() dto: CreateTagDto): Promise { + return this.tagService.createTag(dto); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('project/:projectUuid') + @ApiOperation({ + summary: ControllerRoute.TAG.ACTIONS.GET_TAGS_BY_PROJECT_SUMMARY, + description: ControllerRoute.TAG.ACTIONS.GET_TAGS_BY_PROJECT_DESCRIPTION, + }) + async getTagsByProject( + @Param() params: GetTagsParam, + ): Promise { + return this.tagService.getTagsByProjectUuid(params); + } +} diff --git a/src/tags/dtos/get-tags.param.ts b/src/tags/dtos/get-tags.param.ts new file mode 100644 index 0000000..2f3cc56 --- /dev/null +++ b/src/tags/dtos/get-tags.param.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class GetTagsParam { + @ApiProperty({ + description: 'UUID of the Project', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + projectUuid: string; +} diff --git a/src/tags/dtos/index.ts b/src/tags/dtos/index.ts new file mode 100644 index 0000000..5bbb3c4 --- /dev/null +++ b/src/tags/dtos/index.ts @@ -0,0 +1 @@ +export * from './tags.dto'; diff --git a/src/tags/dtos/tags.dto.ts b/src/tags/dtos/tags.dto.ts new file mode 100644 index 0000000..343d26c --- /dev/null +++ b/src/tags/dtos/tags.dto.ts @@ -0,0 +1,28 @@ +import { IsNotEmpty, IsUUID, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateTagDto { + @ApiProperty({ + description: 'The name of the tag', + example: 'New Tag', + }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ + description: 'UUID of the product associated with the tag', + example: '550e8400-e29b-41d4-a716-446655440000', + }) + @IsUUID() + @IsNotEmpty() + productUuid: string; + + @ApiProperty({ + description: 'UUID of the project associated with the tag', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsUUID() + @IsNotEmpty() + projectUuid: string; +} diff --git a/src/tags/services/index.ts b/src/tags/services/index.ts new file mode 100644 index 0000000..903f6b1 --- /dev/null +++ b/src/tags/services/index.ts @@ -0,0 +1 @@ +export * from './tags.service'; diff --git a/src/tags/services/tags.service.ts b/src/tags/services/tags.service.ts new file mode 100644 index 0000000..466fb23 --- /dev/null +++ b/src/tags/services/tags.service.ts @@ -0,0 +1,91 @@ +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository'; +import { + Injectable, + ConflictException, + NotFoundException, +} from '@nestjs/common'; +import { CreateTagDto } from '../dtos/tags.dto'; +import { ProductEntity } from '@app/common/modules/product/entities'; +import { ProjectEntity } from '@app/common/modules/project/entities'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { NewTagEntity } from '@app/common/modules/tag'; +import { GetTagsParam } from '../dtos/get-tags.param'; + +@Injectable() +export class TagService { + constructor( + private readonly tagRepository: NewTagRepository, + private readonly productRepository: ProductRepository, + private readonly projectRepository: ProjectRepository, + ) {} + async getTagsByProjectUuid(params: GetTagsParam): Promise { + const { projectUuid } = params; + await this.getProjectByUuid(projectUuid); + + const tags = await this.tagRepository.find({ + where: { project: { uuid: projectUuid } }, + relations: ['product', 'project'], + }); + + return new SuccessResponseDto({ + message: `Tags retrieved successfully for project UUID: ${projectUuid}`, + data: tags, + }); + } + + async createTag(dto: CreateTagDto): Promise { + const { name, productUuid, projectUuid } = dto; + + const product = await this.getProductByUuid(productUuid); + const project = await this.getProjectByUuid(projectUuid); + + await this.validateTagUniqueness(name, projectUuid); + + const tag = this.tagRepository.create({ + name, + product, + project, + } as Partial); + + await this.tagRepository.save(tag); + + return new SuccessResponseDto({ + message: `Tag created successfully`, + data: tag, + }); + } + + private async getProductByUuid(uuid: string): Promise { + const product = await this.productRepository.findOne({ where: { uuid } }); + if (!product) { + throw new NotFoundException(`Product with UUID ${uuid} not found.`); + } + return product; + } + + private async getProjectByUuid(uuid: string): Promise { + const project = await this.projectRepository.findOne({ where: { uuid } }); + if (!project) { + throw new NotFoundException(`Project with UUID ${uuid} not found.`); + } + return project; + } + + private async validateTagUniqueness( + name: string, + projectUuid: string, + ): Promise { + const existingTag = await this.tagRepository.findOne({ + where: { name, project: { uuid: projectUuid } }, + }); + + if (existingTag) { + throw new ConflictException( + `Tag with name "${name}" already exists in this project.`, + ); + } + } +} diff --git a/src/tags/tags.module.ts b/src/tags/tags.module.ts new file mode 100644 index 0000000..109e253 --- /dev/null +++ b/src/tags/tags.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TagController } from './controllers'; +import { TagService } from './services'; +import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; + +@Module({ + imports: [ConfigModule], + controllers: [TagController], + providers: [ + TagService, + NewTagRepository, + ProductRepository, + ProjectRepository, + ], + exports: [TagService], +}) +export class TagModule {}