Skip to content

Commit

Permalink
feat: [Contracts] Database and APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
radulescuandrew committed Sep 4, 2024
1 parent 0b23017 commit 7c16460
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 1 deletion.
2 changes: 2 additions & 0 deletions backend/src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { MobileStatisticsController } from './_mobile/statistics/statistics.cont
import { MobileAnouncementsController } from './_mobile/anouncements/anouncements.controller';
import { MobileSettingsController } from './_mobile/settings/settings-controller';
import { MobileNewsController } from './_mobile/news/news.controller';
import { DocumentTemplateController } from './documents/document-template.controller';

@Module({
imports: [UseCaseModule],
Expand All @@ -52,6 +53,7 @@ import { MobileNewsController } from './_mobile/news/news.controller';
DashboardController,
TemplateController,
ContractController,
DocumentTemplateController,
// Mobile
MobileRegularUserController,
MobileAccessRequestController,
Expand Down
49 changes: 49 additions & 0 deletions backend/src/api/documents/document-template.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
import { ExtractUser } from 'src/common/decorators/extract-user.decorator';
import { IAdminUserModel } from 'src/modules/user/models/admin-user.model';
import { CreateDocumentTemplateDto } from './dto/create-document-template.dto';
import { ApiBearerAuth, ApiBody, ApiParam } from '@nestjs/swagger';
import { DocumentTemplatePresenter } from './presenters/document-template.presenter';
import { UuidValidationPipe } from 'src/infrastructure/pipes/uuid.pipe';
import { CreateDocumentTemplateUsecase } from 'src/usecases/documents/create-document-template.usecase';
import { WebJwtAuthGuard } from 'src/modules/auth/guards/jwt-web.guard';
import { GetOneDocumentTemplateUseCase } from 'src/usecases/documents/get-one-document-template.usecase';

@ApiBearerAuth()
@UseGuards(WebJwtAuthGuard)
@Controller('documents/templates')
export class DocumentTemplateController {
constructor(
private readonly createDocumentTemplateUsecase: CreateDocumentTemplateUsecase,
private readonly getOneDocumentTemplateUsecase: GetOneDocumentTemplateUseCase,
) {}

@ApiBody({ type: CreateDocumentTemplateDto })
@Post()
async create(
@Body() payload: CreateDocumentTemplateDto,
@ExtractUser() { organizationId, id: adminId }: IAdminUserModel,
): Promise<DocumentTemplatePresenter> {
const newDocumentTemplate =
await this.createDocumentTemplateUsecase.execute({
...payload,
createdByAdminId: adminId,
organizationId,
});
return new DocumentTemplatePresenter(newDocumentTemplate);
}

@ApiParam({ name: 'id', type: 'string' })
@Get(':id')
async getOne(
@Param('id', UuidValidationPipe) id: string,
@ExtractUser() { organizationId }: IAdminUserModel,
): Promise<DocumentTemplatePresenter> {
const documentTemplate = await this.getOneDocumentTemplateUsecase.execute(
{ id },
organizationId,
);

return new DocumentTemplatePresenter(documentTemplate);
}
}
52 changes: 52 additions & 0 deletions backend/src/api/documents/dto/create-document-template.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Type } from 'class-transformer';
import {
IsNotEmpty,
IsNotEmptyObject,
IsObject,
IsString,
MaxLength,
MinLength,
ValidateNested,
} from 'class-validator';
import { IDocumentTemplateOrganizationData } from 'src/modules/documents/models/document-template.model';

class DocumentTemplateOrganizationDataDto
implements IDocumentTemplateOrganizationData
{
@IsString()
@IsNotEmpty()
officialName: string;

@IsString()
@IsNotEmpty()
registeredOffice: string; // Sediu social

@IsString()
@IsNotEmpty()
CUI: string;

@IsString()
@IsNotEmpty()
legalRepresentativeName: string;

@IsString()
@IsNotEmpty()
legalRepresentativeRole: string;
}

export class CreateDocumentTemplateDto {
@IsString()
@MaxLength(250)
@MinLength(2)
name: string;

@IsObject()
@IsNotEmptyObject()
@ValidateNested()
@Type(() => DocumentTemplateOrganizationDataDto)
organizationData: IDocumentTemplateOrganizationData;

@IsString()
@IsNotEmpty()
documentTerms: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { AdminUserPresenter } from 'src/api/auth/presenters/admin-user.presenter';
import {
IDocumentTemplateModel,
IDocumentTemplateOrganizationData,
} from 'src/modules/documents/models/document-template.model';

export class DocumentTemplatePresenter {
constructor(template: IDocumentTemplateModel) {
this.id = template.id;
this.name = template.name;
this.organizationData = template.organizationData;
this.documentTerms = template.documentTerms;
this.createdByAdmin = template.createdByAdmin
? new AdminUserPresenter(template.createdByAdmin)
: null;
}

@Expose()
@ApiProperty({
description: 'The uuid of the template',
example: '525dcdf9-4117-443e-a0c3-bf652cdc5c1b',
})
id: string;

@Expose()
@ApiProperty({
description: 'The name of the template',
example: 'Template nou',
})
name: string;

@Expose()
@ApiProperty({
description: 'The organization data of the template',
example:
'{ "officialName": "Official name", "registeredOffice": "Registered office (👀 should it be composed?)", "CUI": "RO42211332", "legalRepresentativeName": "Legal representative\'s full name", "legalRepresentativeRole": "Legal representative\'s role"}',
})
organizationData: IDocumentTemplateOrganizationData;

@Expose()
@ApiProperty({
description: 'The document terms of the template',
example: '<p> Some HTML content </p>',
})
documentTerms: string;

@Expose()
@ApiProperty({ description: 'Admin who created the template' })
createdByAdmin: AdminUserPresenter;
}
33 changes: 33 additions & 0 deletions backend/src/migrations/1725454854964-AddDocumentTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class Migrations1725454854964 implements MigrationInterface {
name = 'Migrations1725454854964';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "documents_template" ("deleted_on" TIMESTAMP WITH TIME ZONE, "created_on" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_on" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text NOT NULL, "organization_data" jsonb NOT NULL, "document_terms" text NOT NULL, "organization_id" uuid NOT NULL, "created_by_admin_id" uuid, CONSTRAINT "PK_3c3bacd617c899c37223ebbb037" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_f745fb393b076c4c503437291b" ON "documents_template" ("created_on") `,
);
await queryRunner.query(
`ALTER TABLE "documents_template" ADD CONSTRAINT "FK_0f3775a66466a4be5293db71421" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "documents_template" ADD CONSTRAINT "FK_b879124283b91c84eb3de436a81" FOREIGN KEY ("created_by_admin_id") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "documents_template" DROP CONSTRAINT "FK_b879124283b91c84eb3de436a81"`,
);
await queryRunner.query(
`ALTER TABLE "documents_template" DROP CONSTRAINT "FK_0f3775a66466a4be5293db71421"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_f745fb393b076c4c503437291b"`,
);
await queryRunner.query(`DROP TABLE "documents_template"`);
}
}
14 changes: 13 additions & 1 deletion backend/src/modules/documents/documents.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,27 @@ import { ContractEntity } from './entities/contract.entity';
import { ContractRepositoryService } from './repositories/contract.repository';
import { ContractFacade } from './services/contract.facade';
import { PDFGenerator } from './services/pdf-generator';
import { DocumentTemplateRepositoryService } from './repositories/document-template.repository';
import { DocumentTemplateFacade } from './services/document-template.facade';
import { DocumentTemplateEntity } from './entities/document-template.entity';

@Module({
imports: [TypeOrmModule.forFeature([TemplateEntity, ContractEntity])],
imports: [
TypeOrmModule.forFeature([
TemplateEntity,
ContractEntity,
DocumentTemplateEntity,
]),
],
providers: [
// Repositories
TemplateRepositoryService,
ContractRepositoryService,
DocumentTemplateRepositoryService,
// Facades
TemplateFacade,
ContractFacade,
DocumentTemplateFacade,
// Services
PDFGenerator,
],
Expand All @@ -25,6 +36,7 @@ import { PDFGenerator } from './services/pdf-generator';
TemplateFacade,
ContractFacade,
PDFGenerator,
DocumentTemplateFacade,
],
})
export class DocumentsModule {}
51 changes: 51 additions & 0 deletions backend/src/modules/documents/entities/document-template.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BaseEntity } from 'src/infrastructure/base/base-entity';
import { OrganizationEntity } from 'src/modules/organization/entities/organization.entity';
import { AdminUserEntity } from 'src/modules/user/entities/user.entity';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';

@Entity({ name: 'documents_template' })
export class DocumentTemplateEntity extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ type: 'text', name: 'name' })
name: string;

// {
// "officialName": "Official name",
// "registeredOffice": "Registered office (👀 should it be composed?)",
// "CUI": "RO42211332",
// "legalRepresentativeName": "Legal representative's full name",
// "legalRepresentativeRole": "Legal representative's role"
// }
@Column({ type: 'jsonb', name: 'organization_data' })
organizationData: object;

@Column({ type: 'text', name: 'document_terms' })
documentTerms: string; // HTML string from WYSIWYG

@Column({ type: 'text', name: 'organization_id' })
organizationId: string;

@ManyToOne(() => OrganizationEntity)
@JoinColumn({ name: 'organization_id' })
organization: OrganizationEntity;

@Column({ type: 'string', name: 'created_by_admin_id', nullable: true })
createdByAdminId: string;

@ManyToOne(() => AdminUserEntity)
@JoinColumn({ name: 'created_by_admin_id' })
createdByAdmin: AdminUserEntity;

// @OneToMany(() => ContractEntity, (contract) => contract.template, {
// onDelete: 'SET NULL',
// })
// contracts: ContractEntity[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BusinessException } from 'src/common/interfaces/business-exception.interface';

export enum DocumentTemplateExceptionCodes {
TEMPLATE_001 = 'TEMPLATE_001',
TEMPLATE_002 = 'TEMPLATE_002',
}

type DocumentTemplateExceptionCodeType =
keyof typeof DocumentTemplateExceptionCodes;

export const DocumentTemplateExceptionMessages: Record<
DocumentTemplateExceptionCodes,
BusinessException<DocumentTemplateExceptionCodeType>
> = {
[DocumentTemplateExceptionCodes.TEMPLATE_001]: {
code_error: DocumentTemplateExceptionCodes.TEMPLATE_001,
message: 'Not found',
},
[DocumentTemplateExceptionCodes.TEMPLATE_002]: {
code_error: DocumentTemplateExceptionCodes.TEMPLATE_002,
message: 'Error while creating the document template',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IRepositoryWithPagination } from 'src/common/interfaces/repository-with-pagination.interface';
import {
CreateDocumentTemplateOptions,
FindOneDocumentTemplateOptions,
IDocumentTemplateModel,
} from '../models/document-template.model';
import { DocumentTemplateEntity } from '../entities/document-template.entity';

export interface IDocumentTemplateRepository
extends IRepositoryWithPagination<DocumentTemplateEntity> {
create(
newDocumentTemplate: CreateDocumentTemplateOptions,
): Promise<IDocumentTemplateModel>;
findOne(
findOptions: FindOneDocumentTemplateOptions,
): Promise<IDocumentTemplateModel>;
}
Loading

0 comments on commit 7c16460

Please sign in to comment.