import { Body, Controller, Delete, Get, Path, Post, Put, Route, Security, SuccessResponse, Tags, Request, Response, Example, } from "tsoa"; import minioClient from "../minio"; import esClient from "../elasticsearch"; import { copyCond, listFolder, listItem, pathExist, replaceIllegalChars } from "../utils/minio"; import HttpStatusCode from "../interfaces/http-status"; import { StorageFile, StorageFolder } from "../interfaces/storage-fs"; import HttpError from "../interfaces/http-error"; const DEFAULT_BUCKET = process.env.MINIO_BUCKET; const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX; if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified."); if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."); @Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder") export class SubFolderController extends Controller { /** * @example cabinetName "ตู้เอกสาร 1" * @example drawerName "ลิ้นชัก 1" * @example folderName "แฟ้ม 1" */ @Get("/") @Tags("แฟ้มย่อย") @Security("bearerAuth") @Response( HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง", ) @SuccessResponse(HttpStatusCode.OK, "สำเร็จ") @Example([ { path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1", name: "แฟ้มย่อย 1", createdAt: "2021-07-20T12:33:13.018Z", createdBy: "admin", }, { path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 2", name: "แฟ้มย่อย 2", createdAt: "2022-01-23T16:05:02.114Z", createdBy: "admin", }, ]) public async listFolder( @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, ): Promise { const list = await listFolder( DEFAULT_BUCKET!, `${cabinetName}/${drawerName}/${folderName}`, ).catch((e) => console.error(`Error List Folder: ${e}`)); if (!list) throw new Error("เกิดข้อผิดพลาด ไม่สามารถแสดงรายการแฟ้มได้ กรุณาลองใหม่ในภายหลัง"); return list; } /** * @example cabinetName "ตู้เอกสาร 1" * @example drawerName "ลิ้นชัก 1" * @example folderName "แฟ้ม 1" */ @Post("/") @Tags("แฟ้มย่อย") @Security("bearerAuth", ["admin"]) @Response(HttpStatusCode.NOT_FOUND, "ไม่พบของแฟ้ม") @Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดกับระบบจัดการไฟล์") @SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ") public async createFolder( @Request() request: { user: { preferred_username: string } }, @Body() body: { name: string }, @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, ) { const basePath = `${cabinetName}/${drawerName}/${folderName}/`; if (!(await pathExist(DEFAULT_BUCKET!, basePath))) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบตำแหน่งที่ต้องการสร้างลิ้นชัก"); } const created = await minioClient .putObject(DEFAULT_BUCKET!, `${basePath}${replaceIllegalChars(body.name)}/.keep`, "", 0, { createdAt: new Date().toISOString(), createdBy: request.user.preferred_username, }) .catch((e) => console.error(e)); if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); return this.setStatus(HttpStatusCode.CREATED); } /** * @example cabinetName "ตู้เอกสาร 1" * @example drawerName "ลิ้นชัก 1" * @example folderName "แฟ้ม 1" * @example subFolderName "แฟ้มย่อย 1" */ @Put("/{subFolderName}") @Tags("แฟ้มย่อย") @Security("bearerAuth", ["admin"]) @Response(HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดไม่สามารถย้ายไฟล์ได้") @SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ") public async editFolder( @Body() body: { /** * @example "แฟ้มใหม่" */ name: string; }, @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, @Path() subFolderName: string, ) { const path = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`; const list = await listItem(DEFAULT_BUCKET!, path, true); await Promise.all( list.map(async (current) => { if (!current.name) return; const destination = `${cabinetName}/${drawerName}/${folderName}/${replaceIllegalChars( body.name, )}/${current.name.slice(path.length)}`; const source = `/${DEFAULT_BUCKET}/${current.name}`; return await minioClient .copyObject(DEFAULT_BUCKET!, destination, source, copyCond) .then(async () => { if (current.name.includes(".keep")) { return await minioClient.removeObject(DEFAULT_BUCKET!, current.name); } const search = await esClient.search< StorageFile & { attachment: Record } >({ index: DEFAULT_INDEX!, query: { match: { pathname: current.name } }, }); if (search && search.hits.hits.length === 0) throw new Error("ไม่พบข้อมูลในฐานข้อมูล"); const data = search.hits.hits[0]; await esClient.update({ index: DEFAULT_INDEX!, id: data._id, doc: { pathname: destination }, }); await minioClient.removeObject(DEFAULT_BUCKET!, current.name); }) .catch((e) => { console.error(e); throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้"); }); }), ); return this.setStatus(HttpStatusCode.NO_CONTENT); } /** * @example cabinetName "ตู้เอกสาร 1" * @example drawerName "ลิ้นชัก 1" * @example folderName "แฟ้ม 1" * @example subFolderName "แฟ้มย่อย 1" */ @Delete("/{subFolderName}") @Tags("แฟ้มย่อย") @Security("bearerAuth", ["admin"]) @SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ") public async deleteFolder( @Path() cabinetName: string, @Path() drawerName: string, @Path() folderName: string, @Path() subFolderName: string, ) { await new Promise((resolve, reject) => { const objects: string[] = []; const stream = minioClient.listObjectsV2( DEFAULT_BUCKET!, `${cabinetName}/${drawerName}/${folderName}/${subFolderName}`, true, ); stream.on("data", (v) => { if (v && v.name) objects.push(v.name); }); stream.on("close", async () => resolve(await minioClient.removeObjects(DEFAULT_BUCKET!, objects)), ); stream.on("error", () => reject(new Error("เกิดข้อผิดพลาด ไม่สามารถลบไฟล์ได้"))); }); return this.setStatus(HttpStatusCode.NO_CONTENT); } }