Merge pull request #168 from SyncrowIOT/feat/project-entity

Feat/project-entity
This commit is contained in:
faris Aljohari
2024-12-09 04:50:39 -06:00
committed by GitHub
21 changed files with 422 additions and 1 deletions
@@ -1,4 +1,29 @@
export class ControllerRoute {
static PROJECT = class {
public static readonly ROUTE = 'projects';
static ACTIONS = class {
public static readonly CREATE_PROJECT_SUMMARY = 'Create a new project';
public static readonly CREATE_PROJECT_DESCRIPTION =
'This endpoint allows you to create a new project by providing the required project details.';
public static readonly GET_PROJECT_SUMMARY = 'Retrieve project details';
public static readonly GET_PROJECT_DESCRIPTION =
'This endpoint retrieves the details of a project by its unique identifier (UUID).';
public static readonly UPDATE_PROJECT_SUMMARY = 'Update project details';
public static readonly UPDATE_PROJECT_DESCRIPTION =
'This endpoint updates the details of an existing project using its unique identifier (UUID).';
public static readonly LIST_PROJECTS_SUMMARY = 'List all projects';
public static readonly LIST_PROJECTS_DESCRIPTION =
'This endpoint retrieves a list of all existing projects, including their details.';
public static readonly DELETE_PROJECT_SUMMARY = 'Delete a project';
public static readonly DELETE_PROJECT_DESCRIPTION =
'This endpoint deletes an existing project by its unique identifier (UUID).';
};
};
static REGION = class {
public static readonly ROUTE = 'region';
static ACTIONS = class {
+2 -1
View File
@@ -27,7 +27,7 @@ import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
import { SceneDeviceEntity } from '../modules/scene-device/entities';
import { SpaceProductEntity } from '../modules/space/entities/space-product.entity';
import { ProjectEntity } from '../modules/project/entities';
@Module({
imports: [
TypeOrmModule.forRootAsync({
@@ -42,6 +42,7 @@ import { SpaceProductEntity } from '../modules/space/entities/space-product.enti
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
entities: [
ProjectEntity,
UserEntity,
UserSessionEntity,
UserOtpEntity,
@@ -0,0 +1 @@
export * from './project.dto';
@@ -0,0 +1,15 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class ProjectDto {
@IsString()
@IsNotEmpty()
uuid: string;
@IsString()
@IsNotEmpty()
name: string;
@IsString()
@IsOptional()
public description?: string;
}
@@ -0,0 +1 @@
export * from './project.entity';
@@ -0,0 +1,27 @@
import { Entity, Column, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProjectDto } from '../dtos';
@Entity({ name: 'project' })
@Unique(['name'])
export class ProjectEntity extends AbstractEntity<ProjectDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public name: string;
@Column({ length: 255, nullable: true })
description: string;
constructor(partial: Partial<ProjectEntity>) {
super();
Object.assign(this, partial);
}
}
+1
View File
@@ -0,0 +1 @@
export * from './project.repository.module';
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProjectEntity } from './entities';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [TypeOrmModule.forFeature([ProjectEntity])],
})
export class ProjectEntityModule {}
@@ -0,0 +1 @@
export * from './project.repository';
@@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { ProjectEntity } from '../entities';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ProjectRepository extends Repository<ProjectEntity> {
constructor(private dataSource: DataSource) {
super(ProjectEntity, dataSource.createEntityManager());
}
}
@@ -16,6 +16,9 @@ const mappingInclude: { [key: string]: any } = {
subspace: {
subspace: true,
},
project: {
project: true,
},
};
export function buildTypeORMIncludeQuery(
+2
View File
@@ -22,6 +22,7 @@ import { VisitorPasswordModule } from './vistor-password/visitor-password.module
import { ScheduleModule } from './schedule/schedule.module';
import { SpaceModule } from './space/space.module';
import { ProductModule } from './product';
import { ProjectModule } from './project';
@Module({
imports: [
ConfigModule.forRoot({
@@ -48,6 +49,7 @@ import { ProductModule } from './product';
VisitorPasswordModule,
ScheduleModule,
ProductModule,
ProjectModule,
],
providers: [
{
+1
View File
@@ -0,0 +1 @@
export * from './project.controller';
@@ -0,0 +1,89 @@
import { ControllerRoute } from '@app/common/constants/controller-route';
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ProjectService } from '../services';
import { CreateProjectDto, GetProjectParam } from '../dto';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto';
@ApiTags('Project Module')
@Controller({
version: '1',
path: ControllerRoute.PROJECT.ROUTE,
})
export class ProjectController {
constructor(private readonly projectService: ProjectService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.PROJECT.ACTIONS.CREATE_PROJECT_SUMMARY,
description: ControllerRoute.PROJECT.ACTIONS.CREATE_PROJECT_DESCRIPTION,
})
@Post()
async createProject(@Body() dto: CreateProjectDto): Promise<BaseResponseDto> {
return this.projectService.createProject(dto);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.PROJECT.ACTIONS.UPDATE_PROJECT_SUMMARY,
description: ControllerRoute.PROJECT.ACTIONS.UPDATE_PROJECT_DESCRIPTION,
})
@Put(':projectUuid')
async updateProject(
@Param() params: GetProjectParam,
@Body() dto: CreateProjectDto,
): Promise<BaseResponseDto> {
return this.projectService.updateProject(params.projectUuid, dto);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.PROJECT.ACTIONS.DELETE_PROJECT_SUMMARY,
description: ControllerRoute.PROJECT.ACTIONS.DELETE_PROJECT_DESCRIPTION,
})
@Delete(':projectUuid')
async deleteProject(
@Param() params: GetProjectParam,
): Promise<BaseResponseDto> {
return this.projectService.deleteProject(params.projectUuid);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.PROJECT.ACTIONS.LIST_PROJECTS_SUMMARY,
description: ControllerRoute.PROJECT.ACTIONS.LIST_PROJECTS_DESCRIPTION,
})
@Get()
async list(
@Query() query: PaginationRequestGetListDto,
): Promise<BaseResponseDto> {
return this.projectService.listProjects(query);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: ControllerRoute.PROJECT.ACTIONS.GET_PROJECT_SUMMARY,
description: ControllerRoute.PROJECT.ACTIONS.GET_PROJECT_DESCRIPTION,
})
@Get(':projectUuid')
async findOne(@Param() params: GetProjectParam): Promise<BaseResponseDto> {
return this.projectService.getProject(params.projectUuid);
}
}
+21
View File
@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class CreateProjectDto {
@ApiProperty({
example: 'Project 1',
description: 'Name of the project',
})
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({
example: 'Optional description of the project',
description: 'Description of the project',
required: false,
})
@IsString()
@IsOptional()
description?: string;
}
+11
View File
@@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsUUID } from 'class-validator';
export class GetProjectParam {
@ApiProperty({
description: 'UUID of the Project',
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
})
@IsUUID()
projectUuid: string;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './create-project.dto';
export * from './get-project.param';
+1
View File
@@ -0,0 +1 @@
export * from './project.module';
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ProjectController } from './controllers';
import { ProjectService } from './services';
import { ProjectRepository } from '@app/common/modules/project/repositiories';
@Module({
imports: [],
controllers: [ProjectController],
providers: [ProjectService, ProjectRepository],
exports: [ProjectService],
})
export class ProjectModule {}
+1
View File
@@ -0,0 +1 @@
export * from './project.service';
+185
View File
@@ -0,0 +1,185 @@
import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateProjectDto } from '../dto';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { ProjectEntity } from '@app/common/modules/project/entities';
import {
TypeORMCustomModel,
TypeORMCustomModelFindAllQuery,
} from '@app/common/models/typeOrmCustom.model';
import { ProjectDto } from '@app/common/modules/project/dtos';
import { PageResponse } from '@app/common/dto/pagination.response.dto';
@Injectable()
export class ProjectService {
constructor(private readonly projectRepository: ProjectRepository) {}
async createProject(
createProjectDto: CreateProjectDto,
): Promise<BaseResponseDto> {
const { name } = createProjectDto;
try {
// Check if the project name already exists
const isNameExist = await this.validate(name);
if (isNameExist) {
throw new HttpException(
`Project with name ${name} already exists`,
HttpStatus.CONFLICT,
);
}
const newProject = this.projectRepository.create(createProjectDto);
const savedProject = await this.projectRepository.save(newProject);
return new SuccessResponseDto({
message: `Project with ID ${savedProject.uuid} successfully created`,
data: savedProject,
statusCode: HttpStatus.CREATED,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
'An error occurred while creating the project. Please try again later.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async deleteProject(uuid: string): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Delete the project
await this.projectRepository.delete({ uuid });
return new SuccessResponseDto({
message: `Project with ID ${uuid} successfully deleted`,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while deleting the project ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async listProjects(
pageable: Partial<TypeORMCustomModelFindAllQuery>,
): Promise<BaseResponseDto> {
try {
pageable.modelName = 'project';
const customModel = TypeORMCustomModel(this.projectRepository);
const { baseResponseDto, paginationResponseDto } =
await customModel.findAll(pageable);
return new PageResponse<ProjectDto>(
baseResponseDto,
paginationResponseDto,
);
} catch (error) {
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getProject(uuid: string): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
message: `Project with ID ${uuid} retrieved successfully`,
data: project,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while retrieving the project with id ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async updateProject(
uuid: string,
updateProjectDto: CreateProjectDto,
): Promise<BaseResponseDto> {
try {
// Find the project by UUID
const project = await this.findOne(uuid);
if (!project) {
throw new HttpException(
`Project with ID ${uuid} not found`,
HttpStatus.NOT_FOUND,
);
}
if (updateProjectDto.name && updateProjectDto.name !== project.name) {
const isNameExist = await this.validate(updateProjectDto.name);
if (isNameExist) {
throw new HttpException(
`Project with name ${updateProjectDto.name} already exists`,
HttpStatus.CONFLICT,
);
}
}
// Update the project details
Object.assign(project, updateProjectDto);
const updatedProject = await this.projectRepository.save(project);
return new SuccessResponseDto({
message: `Project with ID ${uuid} successfully updated`,
data: updatedProject,
statusCode: HttpStatus.OK,
});
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
`An error occurred while updating the project with ID ${uuid}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async findOne(uuid: string): Promise<ProjectEntity> {
const project = await this.projectRepository.findOne({ where: { uuid } });
return project;
}
async validate(name: string): Promise<boolean> {
return await this.projectRepository.exists({ where: { name } });
}
}