From 472db99c5005ebb098ecfbb1f2b25eec639e7614 Mon Sep 17 00:00:00 2001 From: Methapon Metanipat Date: Mon, 16 Sep 2024 17:20:51 +0700 Subject: [PATCH] feat: file upload customer branch --- .../03-customer-branch-controller.ts | 414 +++++++++++++----- src/utils/minio.ts | 14 + 2 files changed, 313 insertions(+), 115 deletions(-) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index c1f4045..084e28f 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -17,7 +17,6 @@ import { RequestWithUser } from "../interfaces/user"; import prisma from "../db"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; -import minio, { deleteFolder } from "../services/minio"; import { isSystem } from "../utils/keycloak"; import { branchRelationPermInclude, @@ -27,12 +26,8 @@ import { import { filterStatus } from "../services/prisma"; import { connectOrDisconnect, connectOrNot, whereAddressQuery } from "../utils/relation"; import { notFoundError, relationError } from "../utils/error"; +import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio"; -if (!process.env.MINIO_BUCKET) { - throw Error("Require MinIO bucket."); -} - -const MINIO_BUCKET = process.env.MINIO_BUCKET; const MANAGE_ROLES = [ "system", "head_of_admin", @@ -51,14 +46,6 @@ function globalAllow(user: RequestWithUser["user"]) { const permissionCond = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); -function imageLocation(id: string) { - return `employee/profile-img-${id}`; -} - -function attachmentLocation(customerId: string, branchId: string) { - return `customer/${customerId}/branch/${branchId}`; -} - export type CustomerBranchCreate = { customerId: string; @@ -299,16 +286,7 @@ export class CustomerBranchController extends Controller { ]); return { - result: await Promise.all( - result.map(async (v) => ({ - ...v, - profileImageUrl: await minio.presignedGetObject( - MINIO_BUCKET, - imageLocation(v.id), - 12 * 60 * 60, - ), - })), - ), + result, page, pageSize, total, @@ -514,117 +492,323 @@ export class CustomerBranchController extends Controller { include: { createdBy: true, updatedBy: true }, where: { id: branchId }, }) - .then((v) => { - deleteFolder(MINIO_BUCKET, `${attachmentLocation(record.customerId, branchId)}/`); - return v; - }); + .then((v) => deleteFolder(fileLocation.customerBranch.attachment(branchId)).then(() => v)); } } -@Route("api/v1/customer-branch/{branchId}/attachment") +// @Route("api/v1/customer-branch/{branchId}/attachment") +// @Tags("Customer Branch") +// @Security("keycloak") +// export class CustomerAttachmentController extends Controller { +// @Get() +// async listAttachment(@Path() branchId: string) { +// const record = await prisma.customerBranch.findFirst({ +// where: { id: branchId }, +// }); +// +// if (!record) { +// throw new HttpError( +// HttpStatus.NOT_FOUND, +// "Customer branch cannot be found.", +// "customerBranchNotFound", +// ); +// } +// +// const list = await new Promise((resolve, reject) => { +// const item: string[] = []; +// +// const stream = minio.listObjectsV2( +// MINIO_BUCKET, +// `${attachmentLocation(record.customerId, branchId)}/`, +// ); +// +// stream.on("data", (v) => v && v.name && item.push(v.name)); +// stream.on("end", () => resolve(item)); +// stream.on("error", () => reject(new Error("MinIO error."))); +// }); +// +// return list.map((v) => v.split("/").at(-1) as string); +// } +// +// @Get("{filename}") +// async getAttachment( +// @Request() req: RequestWithUser, +// @Path() branchId: string, +// @Path() filename: string, +// ) { +// const record = await prisma.customerBranch.findFirst({ +// where: { id: branchId }, +// }); +// +// if (!record) { +// throw new HttpError( +// HttpStatus.NOT_FOUND, +// "Customer branch cannot be found.", +// "customerBranchNotFound", +// ); +// } +// +// return await minio.presignedGetObject( +// MINIO_BUCKET, +// `${attachmentLocation(record.customerId, branchId)}/${filename}`, +// 12 * 60 * 60, +// ); +// } +// +// @Put("{filename}") +// async addAttachment( +// @Request() req: RequestWithUser, +// @Path() branchId: string, +// @Path() filename: string, +// ) { +// const record = await prisma.customerBranch.findFirst({ +// where: { id: branchId }, +// }); +// +// if (!record) { +// throw new HttpError( +// HttpStatus.NOT_FOUND, +// "Customer branch cannot be found.", +// "customerBranchNotFound", +// ); +// } +// +// return req.res?.redirect( +// await minio.presignedPutObject( +// MINIO_BUCKET, +// `${attachmentLocation(record.customerId, branchId)}/${filename}`, +// 12 * 60 * 60, +// ), +// ); +// } +// +// @Delete("{filename}") +// async deleteAttachment(@Path() branchId: string, @Path() filename: string) { +// const record = await prisma.customerBranch.findFirst({ +// where: { id: branchId }, +// }); +// +// if (!record) throw notFoundError("Customer Branch"); +// +// await minio.removeObject( +// MINIO_BUCKET, +// `${attachmentLocation(record.customerId, branchId)}/${filename}`, +// { forceDelete: true }, +// ); +// } +// } + +@Route("api/v1/customer-branch/{branchId}") @Tags("Customer Branch") -@Security("keycloak") -export class CustomerAttachmentController extends Controller { - @Get() - async listAttachment(@Path() branchId: string) { - const record = await prisma.customerBranch.findFirst({ - where: { id: branchId }, +export class CustomerBranchFileController extends Controller { + private async checkPermission(user: RequestWithUser["user"], id: string) { + const data = await prisma.customerBranch.findFirst({ + where: { id }, + include: { + customer: { + include: { + registeredBranch: { + include: branchRelationPermInclude(user), + }, + }, + }, + }, }); - - if (!record) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "Customer branch cannot be found.", - "customerBranchNotFound", - ); - } - - const list = await new Promise((resolve, reject) => { - const item: string[] = []; - - const stream = minio.listObjectsV2( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/`, - ); - - stream.on("data", (v) => v && v.name && item.push(v.name)); - stream.on("end", () => resolve(item)); - stream.on("error", () => reject(new Error("MinIO error."))); - }); - - return list.map((v) => v.split("/").at(-1) as string); + if (!data) throw notFoundError("Customer Branch"); + await permissionCheck(user, data.customer.registeredBranch); } - @Get("{filename}") - async getAttachment( - @Request() req: RequestWithUser, - @Path() branchId: string, - @Path() filename: string, - ) { - const record = await prisma.customerBranch.findFirst({ - where: { id: branchId }, - }); - - if (!record) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "Customer branch cannot be found.", - "customerBranchNotFound", - ); - } - - return await minio.presignedGetObject( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/${filename}`, - 12 * 60 * 60, - ); + @Get("attachment") + @Security("keycloak") + async listAttachment(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.attachment(branchId)); } - @Put("{filename}") - async addAttachment( + @Get("attachment/{name}") + @Security("keycloak") + async getAttachment(@Path() branchId: string, @Path() name: string) { + return await getFile(fileLocation.customerBranch.attachment(branchId, name)); + } + + @Put("attachment/{name}") + @Security("keycloak") + async putAttachment( @Request() req: RequestWithUser, @Path() branchId: string, - @Path() filename: string, + @Path() name: string, ) { - const record = await prisma.customerBranch.findFirst({ - where: { id: branchId }, - }); + await this.checkPermission(req.user, branchId); + return req.res?.redirect(await setFile(fileLocation.customerBranch.attachment(branchId, name))); + } - if (!record) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "Customer branch cannot be found.", - "customerBranchNotFound", - ); - } + @Delete("attachment/{name}") + @Security("keycloak") + async delAttachment( + @Request() req: RequestWithUser, + @Path() branchId: string, + @Path() name: string, + ) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.attachment(branchId, name)); + } + @Get("file-citizen") + @Security("keycloak") + async listCitizen(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.attachment(branchId)); + } + + @Get("file-citizen/{id}") + @Security("keycloak") + async getCitizen(@Path() branchId: string, @Path() id: string) { + return await getFile(fileLocation.customerBranch.attachment(branchId, id)); + } + + @Put("file-citizen/{id}") + @Security("keycloak") + async putCitizen(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); + return req.res?.redirect(await setFile(fileLocation.customerBranch.attachment(branchId, id))); + } + + @Delete("file-citizen/{id}") + @Security("keycloak") + async delCitizen(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.citizen(branchId, id)); + } + + @Get("file-power-of-attorney") + @Security("keycloak") + async listPoa(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.powerOfAttorney(branchId)); + } + + @Get("file-power-of-attorney/{id}") + @Security("keycloak") + async getPoa(@Path() branchId: string, @Path() id: string) { + return await getFile(fileLocation.customerBranch.powerOfAttorney(branchId, id)); + } + + @Put("file-power-of-attorney/{id}") + @Security("keycloak") + async putPoa(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); return req.res?.redirect( - await minio.presignedPutObject( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/${filename}`, - 12 * 60 * 60, - ), + await setFile(fileLocation.customerBranch.powerOfAttorney(branchId, id)), ); } - @Delete("{filename}") - async deleteAttachment(@Path() branchId: string, @Path() filename: string) { - const record = await prisma.customerBranch.findFirst({ - where: { id: branchId }, - }); + @Delete("file-power-of-attorney/{id}") + @Security("keycloak") + async delPoa(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.powerOfAttorney(branchId, id)); + } - if (!record) { - throw new HttpError( - HttpStatus.NOT_FOUND, - "Customer branch cannot be found.", - "customerBranchNotFound", - ); - } + @Get("file-house-registration") + @Security("keycloak") + async listHouseRegis(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.houseRegistration(branchId)); + } - await minio.removeObject( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/${filename}`, - { forceDelete: true }, + @Get("file-house-registration/{id}") + @Security("keycloak") + async getHouseRegis(@Path() branchId: string, @Path() id: string) { + return await getFile(fileLocation.customerBranch.houseRegistration(branchId, id)); + } + + @Put("file-house-registration/{id}") + @Security("keycloak") + async putHouseRegis( + @Request() req: RequestWithUser, + @Path() branchId: string, + @Path() id: string, + ) { + await this.checkPermission(req.user, branchId); + return req.res?.redirect( + await setFile(fileLocation.customerBranch.houseRegistration(branchId, id)), ); } + + @Delete("file-house-registration/{id}") + @Security("keycloak") + async delHouseRegis( + @Request() req: RequestWithUser, + @Path() branchId: string, + @Path() id: string, + ) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.houseRegistration(branchId, id)); + } + + @Get("file-commercial-registration") + @Security("keycloak") + async listCommercialRegis(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.commercialRegistration(branchId)); + } + + @Get("file-commercial-registration/{id}") + @Security("keycloak") + async getCommercialRegis(@Path() branchId: string, @Path() id: string) { + return await getFile(fileLocation.customerBranch.commercialRegistration(branchId, id)); + } + + @Put("file-commercial-registration/{id}") + @Security("keycloak") + async putCommercialRegis( + @Request() req: RequestWithUser, + @Path() branchId: string, + @Path() id: string, + ) { + await this.checkPermission(req.user, branchId); + return req.res?.redirect( + await setFile(fileLocation.customerBranch.commercialRegistration(branchId, id)), + ); + } + + @Delete("file-commercial-registration/{id}") + @Security("keycloak") + async delCommercialRegis( + @Request() req: RequestWithUser, + @Path() branchId: string, + @Path() id: string, + ) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.commercialRegistration(branchId, id)); + } + + @Get("file-vat-registration") + @Security("keycloak") + async listVatRegis(@Request() req: RequestWithUser, @Path() branchId: string) { + await this.checkPermission(req.user, branchId); + return await listFile(fileLocation.customerBranch.vatRegistration(branchId)); + } + + @Get("file-vat-registration/{id}") + @Security("keycloak") + async getVatRegis(@Path() branchId: string, @Path() id: string) { + return await getFile(fileLocation.customerBranch.vatRegistration(branchId, id)); + } + + @Put("file-vat-registration/{id}") + @Security("keycloak") + async putVatRegis(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); + return req.res?.redirect( + await setFile(fileLocation.customerBranch.vatRegistration(branchId, id)), + ); + } + + @Delete("file-vat-registration/{id}") + @Security("keycloak") + async delVatRegis(@Request() req: RequestWithUser, @Path() branchId: string, @Path() id: string) { + await this.checkPermission(req.user, branchId); + return await deleteFile(fileLocation.customerBranch.vatRegistration(branchId, id)); + } } diff --git a/src/utils/minio.ts b/src/utils/minio.ts index 8c93a40..bb2bd8a 100644 --- a/src/utils/minio.ts +++ b/src/utils/minio.ts @@ -60,6 +60,20 @@ export const fileLocation = { customer: { img: (customerId: string, name?: string) => `customer/img-${customerId}/${name || ""}`, }, + customerBranch: { + attachment: (customerBranchId: string, name?: string) => + `customer-branch/attachment-${customerBranchId}/${name || ""}`, + citizen: (customerBranchId: string, citizenId?: string) => + `customer-branch/citizen-${customerBranchId}/${citizenId || ""}`, + houseRegistration: (customerBranchId: string, houseRegistrationId?: string) => + `customer-branch/house-registration-${customerBranchId}/${houseRegistrationId || ""}`, + commercialRegistration: (customerBranchId: string, commercialRegistrationId?: string) => + `customer-branch/commercial-registration-${customerBranchId}/${commercialRegistrationId || ""}`, + vatRegistration: (customerBranchId: string, vatRegistrationId?: string) => + `customer-branch/vat-registration-${customerBranchId}/${vatRegistrationId || ""}`, + powerOfAttorney: (customerBranchId: string, powerOfAttorneyId?: string) => + `customer-branch/power-of-attorney-${customerBranchId}/${powerOfAttorneyId || ""}`, + }, employee: { img: (employeeId: string, name?: string) => `employee/img-${employeeId}/${name || ""}`, attachment: (employeeId: string, name?: string) =>