From 5be70cff2eb385ad78185a49ec89a59326686a57 Mon Sep 17 00:00:00 2001 From: Vins Date: Fri, 16 Feb 2024 15:00:07 +0100 Subject: [PATCH] Ondoing --- package.json | 3 +- src/app/api/notary/BucketController.ts | 126 ++++++++++++++++++ src/app/api/notary/OfficesController.ts | 3 + src/app/index.ts | 2 + .../20240215123932_rib/migration.sql | 2 + .../20240215142816_rib_optional/migration.sql | 2 + .../20240216104224_rib_name/migration.sql | 2 + src/common/databases/schema.prisma | 2 + src/common/databases/seeders/prod-seeder.ts | 2 +- src/common/databases/seeders/seeder.ts | 2 +- .../repositories/DocumentTypesRepository.ts | 4 +- src/common/repositories/OfficesRepository.ts | 2 + .../common/BucketService/BucketService.ts | 77 +++++++++++ .../notary/OfficesService/OfficesService.ts | 12 +- src/test/config/Init.ts | 2 +- 15 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 src/app/api/notary/BucketController.ts create mode 100644 src/common/databases/migrations/20240215123932_rib/migration.sql create mode 100644 src/common/databases/migrations/20240215142816_rib_optional/migration.sql create mode 100644 src/common/databases/migrations/20240216104224_rib_name/migration.sql create mode 100644 src/services/common/BucketService/BucketService.ts diff --git a/package.json b/package.json index 1cb06594..b9393cad 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@prisma/client": "^4.11.0", "@sentry/node": "^7.91.0", "adm-zip": "^0.5.10", + "aws-sdk": "^2.1556.0", "axios": "^1.6.2", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -58,7 +59,7 @@ "file-type-checker": "^1.0.8", "fp-ts": "^2.16.1", "jsonwebtoken": "^9.0.0", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.108", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.115", "module-alias": "^2.2.2", "monocle-ts": "^2.3.13", "multer": "^1.4.5-lts.1", diff --git a/src/app/api/notary/BucketController.ts b/src/app/api/notary/BucketController.ts new file mode 100644 index 00000000..24c4f2ac --- /dev/null +++ b/src/app/api/notary/BucketController.ts @@ -0,0 +1,126 @@ +import { Response, Request } from "express"; +import { Controller, Delete, Get, Post } from "@ControllerPattern/index"; +import ApiController from "@Common/system/controller-pattern/ApiController"; + +import { Service } from "typedi"; +import BucketService from "@Services/common/BucketService/BucketService"; +import authHandler from "@App/middlewares/AuthHandler"; +import OfficesService from "@Services/notary/OfficesService/OfficesService"; +import { Office as OfficeResource } from "le-coffre-resources/dist/Notary"; + +@Controller() +@Service() +export default class BucketController extends ApiController { + constructor(private bucketService: BucketService, private officesService: OfficesService) { + super(); + } + + @Get("/api/v1/notary/bucket/:uid", [authHandler]) + protected async getRibStream(req: Request, response: Response) { + const uid = req.params["uid"]; + if (!uid) { + this.httpBadRequest(response, "No uid provided"); + return; + } + + const office = await this.officesService.getByUid(uid, {address: true}); + + if(!office) { + this.httpNotFoundRequest(response, "Office not found"); + return; + } + + const fileName = office.rib_name; + if(!fileName) { + return; + } + + try { + const file = await this.bucketService.getByUid(uid, fileName); + response.attachment(fileName); + response.send(file.Body); + } catch (error) { + this.httpInternalError(response, error); + } + } + + @Post("/api/v1/notary/bucket", [authHandler]) + protected async post(req: Request, response: Response) { + try { + const officeId: string = req.body.user.office_Id + if (!req.file) throw new Error("No file provided"); + if (!officeId) throw new Error("No officeId provided"); + + const office = await this.officesService.getByUid(officeId, {address: true}); + + if (!office) { + this.httpNotFoundRequest(response, "office not found"); + return; + } + const fileUrl = await this.bucketService.createOrUpdate(officeId, req.file); + + if(!fileUrl || !req.file.originalname ) throw new Error("Error while uploading file"); + + office.rib_url = fileUrl; + office.rib_name = req.file.originalname; + + const officeEntity = OfficeResource.hydrate(office, { + strategy: "excludeAll", + }); + + //call service to get prisma entity + const officeEntityUpdated = await this.officesService.update(officeId, officeEntity); + //Hydrate ressource with prisma entity + const officeUpdated = OfficeResource.hydrate(officeEntityUpdated, { + strategy: "excludeAll", + }); + + //success + this.httpCreated(response, officeUpdated); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } + + @Delete("/api/v1/notary/bucket/:uid", [authHandler]) + protected async delete(req: Request, response: Response) { + try { + const officeId: string = req.body.user.office_Id + if (!officeId) throw new Error("No officeId provided"); + + const office = await this.officesService.getByUid(officeId, {address: true}); + console.log(office); + + + if (!office) { + this.httpNotFoundRequest(response, "office not found"); + return; + } + + const fileName = office.rib_name; + + await this.bucketService.delete(officeId, fileName!); + + office.rib_url = null; + office.rib_name = null; + + const officeEntity = OfficeResource.hydrate(office, { + strategy: "excludeAll", + }); + + //call service to get prisma entity + const officeEntityUpdated = await this.officesService.update(officeId, officeEntity); + //Hydrate ressource with prisma entity + const officeUpdated = OfficeResource.hydrate(officeEntityUpdated, { + strategy: "excludeAll", + }); + + //success + this.httpCreated(response, officeUpdated); + } catch (error) { + this.httpInternalError(response, error); + return; + } + } +} diff --git a/src/app/api/notary/OfficesController.ts b/src/app/api/notary/OfficesController.ts index ec418381..d738ca99 100644 --- a/src/app/api/notary/OfficesController.ts +++ b/src/app/api/notary/OfficesController.ts @@ -69,6 +69,7 @@ export default class OfficesController extends ApiController { } const officeEntity = await this.officesService.getByUid(uid, query); + if (!officeEntity) { this.httpNotFoundRequest(response, "office not found"); @@ -77,6 +78,8 @@ export default class OfficesController extends ApiController { //Hydrate ressource with prisma entity const office = OfficeResource.hydrate(officeEntity, { strategy: "excludeAll" }); + + //success this.httpSuccess(response, office); } catch (error) { diff --git a/src/app/index.ts b/src/app/index.ts index 2de03a00..3646fda0 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -46,6 +46,7 @@ import DocumentControllerId360 from "./api/id360/DocumentController"; import CustomerControllerId360 from "./api/id360/CustomerController"; import UserNotificationController from "./api/notary/UserNotificationController"; import AuthController from "./api/customer/AuthController"; +import BucketController from "./api/notary/BucketController"; /** * @description This allow to declare all controllers used in the application @@ -100,5 +101,6 @@ export default { Container.get(DocumentControllerId360); Container.get(CustomerControllerId360); Container.get(AuthController); + Container.get(BucketController); }, }; diff --git a/src/common/databases/migrations/20240215123932_rib/migration.sql b/src/common/databases/migrations/20240215123932_rib/migration.sql new file mode 100644 index 00000000..119d529e --- /dev/null +++ b/src/common/databases/migrations/20240215123932_rib/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "offices" ADD COLUMN "rib_url" VARCHAR(255) DEFAULT ''; diff --git a/src/common/databases/migrations/20240215142816_rib_optional/migration.sql b/src/common/databases/migrations/20240215142816_rib_optional/migration.sql new file mode 100644 index 00000000..20107944 --- /dev/null +++ b/src/common/databases/migrations/20240215142816_rib_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "offices" ALTER COLUMN "rib_url" DROP DEFAULT; diff --git a/src/common/databases/migrations/20240216104224_rib_name/migration.sql b/src/common/databases/migrations/20240216104224_rib_name/migration.sql new file mode 100644 index 00000000..f733246c --- /dev/null +++ b/src/common/databases/migrations/20240216104224_rib_name/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "offices" ADD COLUMN "rib_name" VARCHAR(255); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index de4afe97..06b7518b 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -92,6 +92,8 @@ model Offices { created_at DateTime? @default(now()) updated_at DateTime? @updatedAt checked_at DateTime? + rib_url String? @db.VarChar(255) + rib_name String? @db.VarChar(255) deed_types DeedTypes[] users Users[] office_folders OfficeFolders[] diff --git a/src/common/databases/seeders/prod-seeder.ts b/src/common/databases/seeders/prod-seeder.ts index b2e816bf..b6151603 100644 --- a/src/common/databases/seeders/prod-seeder.ts +++ b/src/common/databases/seeders/prod-seeder.ts @@ -1264,7 +1264,7 @@ export default async function main() { const documentTypeCreated = await prisma.documentTypes.create({ data: { name: documentType.name, - public_description: documentType.public_description, + public_description: documentType.public_description || "", private_description: documentType.private_description, office: { connect: { diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 2e7b3f30..ce7e4cdf 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -1975,7 +1975,7 @@ export default async function main() { const documentTypeCreated = await prisma.documentTypes.create({ data: { name: documentType.name, - public_description: documentType.public_description, + public_description: documentType.public_description || "", private_description: documentType.private_description, office: { connect: { diff --git a/src/common/repositories/DocumentTypesRepository.ts b/src/common/repositories/DocumentTypesRepository.ts index 221e541f..790412b9 100644 --- a/src/common/repositories/DocumentTypesRepository.ts +++ b/src/common/repositories/DocumentTypesRepository.ts @@ -30,7 +30,7 @@ export default class DocumentTypesRepository extends BaseRepository { const createArgs: Prisma.DocumentTypesCreateArgs = { data: { name: documentType.name, - public_description: documentType.public_description, + public_description: documentType.public_description || "", private_description: documentType.private_description, office: { connect: { @@ -52,7 +52,7 @@ export default class DocumentTypesRepository extends BaseRepository { }, data: { name: documentType.name, - public_description: documentType.public_description, + public_description: documentType.public_description || "", private_description: documentType.private_description, archived_at: documentType.archived_at, }, diff --git a/src/common/repositories/OfficesRepository.ts b/src/common/repositories/OfficesRepository.ts index fb763880..a937bac0 100644 --- a/src/common/repositories/OfficesRepository.ts +++ b/src/common/repositories/OfficesRepository.ts @@ -67,6 +67,8 @@ export default class OfficesRepository extends BaseRepository { }, }, office_status: EOfficeStatus[office.office_status as keyof typeof EOfficeStatus], + rib_url: office.rib_url, + rib_name: office.rib_name, }, }; return this.model.update(updateArgs); diff --git a/src/services/common/BucketService/BucketService.ts b/src/services/common/BucketService/BucketService.ts new file mode 100644 index 00000000..ff29737a --- /dev/null +++ b/src/services/common/BucketService/BucketService.ts @@ -0,0 +1,77 @@ +import BaseService from "@Services/BaseService"; +import { Service } from "typedi"; +import * as AWS from "aws-sdk"; +import { BackendVariables } from "@Common/config/variables/Variables"; +import path from "path"; + +// Configure the AWS SDK for Scaleway +const s3 = new AWS.S3({ + accessKeyId: "SCWZ39ZVQWXFC7HA0647", + secretAccessKey: "59bcf27d-bee3-4d14-8b4d-03fd6a8be6cd", + endpoint: "https://lecoffre-bucket.s3.fr-par.scw.cloud", // Use the appropriate Scaleway endpoint + s3ForcePathStyle: true, // Needed for Scaleway's S3-compatible API + signatureVersion: "v4", +}); + +@Service() +export default class BucketService extends BaseService { + constructor(private variables: BackendVariables) { + super(); + } + + public async download() { + + } + + public async getByUid(uid: string, fileName: string) { + const key = path.join(this.variables.ENV, uid, fileName); + + return new Promise(async (resolve, reject) => { + s3.getObject( + { + Bucket: "lecoffre-bucket", + Key: key, + }, + function (err, data) { + if (err) return reject(err); + resolve(data); + }, + ); + }); + } + + public async createOrUpdate(officeId: string, file: Express.Multer.File) { + const key = path.join(this.variables.ENV, officeId, file.originalname); + + const uploadParams = { + Bucket: "lecoffre-bucket", + Key: key, // Example: 'example.txt' + Body: file.buffer, // Example: fs.createReadStream('/path/to/file') + ACL: "public-read", // Optional: Set the ACL if needed + }; + + return new Promise((resolve, reject) => { + s3.putObject(uploadParams, function (err, data) { + if (err) return reject(err); + resolve(`https://lecoffre-bucket.s3.fr-par.scw.cloud/lecoffre-bucket/${key}`); + }); + }); + } + + public async delete(officeId: string, fileName: string) { + const key = path.join(this.variables.ENV, officeId, fileName); + + return new Promise(async (resolve, reject) => { + s3.getObject( + { + Bucket: "lecoffre-bucket", + Key: key, + }, + function (err, data) { + if (err) return reject(err); + resolve(data); + }, + ); + }); + } +} diff --git a/src/services/notary/OfficesService/OfficesService.ts b/src/services/notary/OfficesService/OfficesService.ts index af25083b..af0bb8dd 100644 --- a/src/services/notary/OfficesService/OfficesService.ts +++ b/src/services/notary/OfficesService/OfficesService.ts @@ -1,7 +1,9 @@ -import { Prisma } from "@prisma/client"; +import { Offices, Prisma } from "@prisma/client"; import OfficesRepository from "@Repositories/OfficesRepository"; import BaseService from "@Services/BaseService"; import { Service } from "typedi"; +import { Office as OfficeRessource } from "le-coffre-resources/dist/Notary"; + @Service() export default class OfficesService extends BaseService { @@ -24,4 +26,12 @@ export default class OfficesService extends BaseService { public async getByUid(uid: string, query?: Prisma.OfficesInclude) { return this.officeRepository.findOneByUid(uid, query); } + + /** + * @description : Modify an office + * @throws {Error} If office cannot be modified + */ + public async update(uid: string, officeEntity: OfficeRessource): Promise { + return this.officeRepository.update(uid, officeEntity); + } } diff --git a/src/test/config/Init.ts b/src/test/config/Init.ts index 4541cb77..919f934d 100644 --- a/src/test/config/Init.ts +++ b/src/test/config/Init.ts @@ -36,7 +36,7 @@ export const initDocumentType = (documentType: DocumentType, office: Office): Pr return prisma.documentTypes.create({ data: { name: documentType.name, - public_description: documentType.public_description, + public_description: documentType.public_description || "" , private_description: documentType.private_description, archived_at: null, office_uid: office.uid!,