From 3ff307720bb6138659684bf705b6999bb2f1e2bd Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 21 Nov 2023 11:46:40 +0700 Subject: [PATCH] feat: subfolder file crud --- .../controllers/subFolderFileController.ts | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 Prototype/server/src/controllers/subFolderFileController.ts diff --git a/Prototype/server/src/controllers/subFolderFileController.ts b/Prototype/server/src/controllers/subFolderFileController.ts new file mode 100644 index 0000000..93605ea --- /dev/null +++ b/Prototype/server/src/controllers/subFolderFileController.ts @@ -0,0 +1,290 @@ +import { + Controller, + Delete, + FormField, + Get, + Patch, + Path, + Post, + Request, + Route, + Security, + SuccessResponse, + Tags, + UploadedFile, +} from "tsoa"; +import esClient from "../elasticsearch"; +import minioClient from "../storage"; +import HttpStatusCode from "../interfaces/http-status"; +import { pathExist } from "../utils/minio"; +import HttpError from "../interfaces/http-error"; +import { EhrFile } from "../interfaces/ehr-fs"; + +@Route( + "/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder/{subFolderName}/file", +) +export class SubFolderFileController extends Controller { + @Post("/") + @Tags("SubFolder File") + @Security("bearerAuth") + @SuccessResponse(HttpStatusCode.CREATED) + public async uploadFile( + @Request() request: { user: { preferred_username: string } }, + @UploadedFile() file: Express.Multer.File, + @FormField() title: string, + @FormField() description: string, + @FormField() keyword: string, + @FormField() category: string, + @Path() cabinetName: string, + @Path() drawerName: string, + @Path() folderName: string, + @Path() subFolderName: string, + ) { + const filename = Buffer.from(file.originalname, "latin1").toString("utf-8"); + const pathname = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${filename}`; + + if (!(await pathExist(`${cabinetName}/${drawerName}/${folderName}/${subFolderName}`))) { + throw new HttpError( + HttpStatusCode.PRECONDITION_FAILED, + "Cabinet, drawer, folder or subfolder cannot be found.", + ); + } + + const info = await minioClient + .putObject("ehr", pathname, file.buffer, file.size, { + "Content-Type": file.mimetype, + createdAt: new Date().toISOString(), + createdBy: request.user.preferred_username, + }) + .catch((e) => console.error(e)); + + if (!info) throw new Error("Object storage error occured."); + + const search = await esClient.search }>({ + index: "ehr-api-client", + query: { + match: { + pathname: pathname, + }, + }, + }); + + const exist = search.hits.hits.find((v) => v._source?.pathname === pathname); + + const metadata: Partial = { + pathname, + fileName: filename, + fileSize: file.size, + fileType: file.mimetype, + title: title, + description: description, + category: category.split(","), + keyword: keyword.split(","), + }; + + if (!exist) { + await esClient.index({ + pipeline: "attachment", + index: "ehr-api-client", + document: { + data: Buffer.from(file.buffer).toString("base64"), + createdAt: new Date().toISOString(), + createdBy: request.user.preferred_username, + updatedAt: new Date().toISOString(), + updatedBy: request.user.preferred_username, + ...metadata, + }, + }); + } else { + await esClient.delete({ index: exist._index, id: exist._id }); + await esClient.index({ + pipeline: "attachment", + index: "ehr-api-client", + document: { + data: Buffer.from(file.buffer).toString("base64"), + createdAt: exist._source?.createdAt, + createdBy: exist._source?.createdBy, + updatedAt: new Date().toISOString(), + updatedBy: request.user.preferred_username, + ...metadata, + }, + }); + } + + return this.setStatus(HttpStatusCode.CREATED); + } + + @Get("/") + @Tags("SubFolder File") + @SuccessResponse(HttpStatusCode.OK) + public async getFile( + @Path() cabinetName: string, + @Path() drawerName: string, + @Path() folderName: string, + @Path() subFolderName: string, + ) { + const search = await esClient.search< + EhrFile & { + attachment: Record; + } + >({ + index: "ehr-api-client", + query: { + prefix: { + pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}`, + }, + }, + }); + + // Use flatMap for return type only. Filter does not change type after filter out undefined or null + const records = search.hits.hits + .map((v) => { + if (!v._source) return; + + const { attachment, ...rest } = v._source; + + return rest; + }) + .flatMap((v) => (v ? [v] : [])); + + return records; + } + + @Patch("/{fileName}") + @Tags("SubFolder File") + @Security("bearerAuth") + @SuccessResponse(HttpStatusCode.OK) + public async updateFile( + @Request() request: { user: { preferred_username: string } }, + @Path() cabinetName: string, + @Path() drawerName: string, + @Path() folderName: string, + @Path() subFolderName: string, + @Path() fileName: string, + @UploadedFile() file?: Express.Multer.File, + @FormField() title?: string, + @FormField() description?: string, + @FormField() keyword?: string, + @FormField() category?: string, + ) { + const search = await esClient.search }>({ + index: "ehr-api-client", + query: { + match: { + pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`, + }, + }, + }); + + if (search && search.hits.hits.length === 0) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "Not found"); + } + + const data = search.hits.hits[0]; + + if (!file) { + const esResult = await esClient + .update({ + index: "ehr-api-client", + id: data._id, + doc: { + title, + description, + keyword: keyword?.split(","), + category: category?.split(","), + updatedAt: new Date().toISOString(), + updatedBy: request.user.preferred_username, + }, + }) + .catch((e) => console.error(e)); + + if (!esResult) throw new Error("An error occured, cannot perform this action."); + } else { + const filename = Buffer.from(file.originalname, "latin1").toString("utf-8"); + const pathname = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${filename}`; + + await minioClient.removeObject( + "ehr", + `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`, + ); + + const info = await minioClient + .putObject("ehr", pathname, file.buffer, file.size, { + "Content-Type": file.mimetype, + createdAt: new Date().toISOString(), + createdBy: request.user.preferred_username, + }) + .catch((e) => console.error(e)); + + if (!info) throw new Error("Object storage error occured."); + + await esClient.delete({ index: data._index, id: data._id }); + await esClient.index({ + pipeline: "attachment", + index: "ehr-api-client", + document: { + data: Buffer.from(file.buffer).toString("base64"), + pathname, + fileName: filename, + fileSize: file.size, + fileType: file.mimetype, + title: title, + description: description, + category: category?.split(","), + keyword: keyword?.split(","), + createdAt: data._source?.createdAt, + createdBy: data._source?.createdBy, + updatedAt: new Date().toISOString(), + updatedBy: request.user.preferred_username, + }, + }); + } + + return this.setStatus(HttpStatusCode.NO_CONTENT); + } + + @Delete("/{fileName}") + @Tags("SubFolder File") + @Security("bearerAuth") + @SuccessResponse(HttpStatusCode.OK) + public async deleteFile( + @Path() cabinetName: string, + @Path() drawerName: string, + @Path() folderName: string, + @Path() subFolderName: string, + @Path() fileName: string, + ) { + const search = await esClient.search< + EhrFile & { + attachment: Record; + } + >({ + index: "ehr-api-client", + query: { + match: { + pathname: `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`, + }, + }, + }); + + if (search && search.hits.hits.length === 0) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "Not found"); + } + + const esResult = await esClient + .delete({ + index: "ehr-api-client", + id: search.hits.hits[0]._id, + }) + .catch((e) => console.error(e)); + + if (!esResult) throw new Error("An error occured, cannot perform this action."); + + await minioClient.removeObject( + "ehr", + `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/${fileName}`, + ); + + return this.setStatus(HttpStatusCode.NO_CONTENT); + } +}