diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts new file mode 100644 index 0000000..a383bf3 --- /dev/null +++ b/src/controllers/StorageController.ts @@ -0,0 +1,278 @@ +import { + Body, + Controller, + Delete, + Example, + Get, + Patch, + Path, + Post, + Route, + SuccessResponse, + Tags, +} from "tsoa"; +import HttpStatus from "../interfaces/http-status"; +import { + createFile, + createFolder, + deleteFile, + deleteFolder, + downloadFile, + listFile, + updateFile, +} from "../services/edm"; + +@Route("api/v1/salary/file") +@Tags("Document") +export class DocumentController extends Controller { + /** + * @example id "00000000-0000-0000-0000-000000000000" + * + * @summary ข้อมูลเอกสารทั้ง + */ + @Get("{id}") + @SuccessResponse(200, "สำเร็จ") + @Example([ + { + pathname: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", + path: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000", + title: "เอกสาร 1", + description: "", + author: "นายก", + metadata: { tag: 1 }, + keyword: [], + category: [], + fileName: "เอกสาร 1.docx", + fileType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + fileSize: 1024, + upload: true, + createdAt: "2021-07-20T12:33:13.018Z", + createdBy: "service-account-ext-api", + updatedAt: "2021-07-20T12:33:13.018Z", + updatedBy: "service-account-ext-api", + }, + ]) + public async getFile(@Path() id: string) { + const list = await listFile(["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน", id]); + if (!list) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); + return list; + } + + /** + * ข้อควรระวัง: หากลิงก์ยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ + * + * @example id "00000000-0000-0000-0000-000000000000" + * @example file "เอกสาร 1.docx" + * + * @summary ข้อมูลเอกสารพร้อมลิงก์ดาวน์โหลด + */ + @Get("/{id}/{file}") + @SuccessResponse(200, "สำเร็จ") + @Example({ + pathname: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", + path: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000", + title: "เอกสาร 1", + description: "", + author: "นายก", + keyword: [], + category: [], + metadata: { tag: 1 }, + fileName: "เอกสาร 1.docx", + fileType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + fileSize: 1024, + hidden: false, + upload: true, + createdAt: "2021-07-20T12:33:13.018Z", + createdBy: "service-account-ext-api", + updatedAt: "2021-07-20T12:33:13.018Z", + updatedBy: "service-account-ext-api", + downloadUrl: "https://.../...", // Presigned Download URL 7 Days Exp + }) + public async getFileDownload(@Path() id: string, @Path() file: string) { + const data = await downloadFile(["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน", id], file); + if (!data) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); + return data; + } + + /** + * ข้อควรระวัง: หากลิงก์ภาษาไทยยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ (น่าจะเป็นปัญหาทางด้านเทคนิคของ DNS) + * + * เมื่ออัปโหลดไฟล์โดย PUT Method จำเป็นต้องแนบ Content-Type ที่ถูกต้องของไฟล์ไปด้วยเพื่อให้ระบบรู้จักไฟล์นั้นๆ + * โดย Content-Type จะเป็น mime-type เช่น docx เป็น application/vnd.openxmlformats-officedocument.wordprocessingml.document + * ทั้งนี้ Content-Type อาจจะต่างกันแม้นามสกุลจะเหมือนกันได้ + * + * โดยปกติเมื่อเลือกไฟล์แล้ว Browser จะเก็บประเภทของไฟล์ไว้ด้วย ซึ่งเป็น Object ของ File มี attribute ชื่อ type ซึ่งเก็บ mime-type ไว้ + * + * หากไม่มีการป้อนชื่อ title ระบบ EDM จะใช้ title เป็นชื่อเดียวกับ ชื่อไฟล์โดยอัตโนมัติ + * + * @example id "00000000-0000-0000-0000-000000000000" + * + * @summary ร้องขอการอัปโหลดเอกสาร + */ + @Post("{id}") + @Example([ + { + pathname: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", + path: "ระบบเงินเดือน/00000000-0000-0000-0000-000000000000", + title: "เอกสาร 1", + description: "", + author: "นายก", + keyword: [], + category: [], + metadata: { + tag: 1, + }, + fileName: "เอกสาร 1.docx", + fileSize: 0, + fileType: "", + hidden: false, + upload: false, + createdAt: "2021-07-20T12:33:13.018Z", + createdBy: "service-account-ext-api", + updatedAt: "2021-07-20T12:33:13.018Z", + updatedBy: "service-account-ext-api", + uploadUrl: "https://.../...", // Presigned Upload URL 7 Days Exp + }, + ]) + @SuccessResponse(200, "Success") + public async uploadFile( + @Path() id: string, + @Body() + body: { + /** + * @example [ + * { "fileName": "เอกสาร 1.docx" }, + * { "fileName": "เอกสาร 2.docx", "title": "เอกสาร 2" } + * ] + */ + fileList: { + fileName: string; + title?: string; + description?: string; + keyword?: string[]; + category?: string[]; + author?: string; + metadata?: { + [key: string]: unknown; + }; + }[]; + /** + * @example false + */ + replace?: boolean; + }, + ) { + const path = ["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน", id]; + + if (!(await createFolder(path, id, true))) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); + } + const list = await listFile(path); + + if (!list || !Array.isArray(list)) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); + } + + let used: string[] = []; + + let fileList = !body.replace + ? body.fileList.map(({ fileName, ...props }) => { + const dotIndex = fileName.lastIndexOf("."); + const originalName = + dotIndex !== -1 && !fileName.startsWith(".") ? fileName.slice(0, dotIndex) : fileName; + const extension = + dotIndex !== -1 && !fileName.startsWith(".") ? fileName.slice(dotIndex) : ""; + + let i = 1; + while (list.findIndex((v) => v.fileName === fileName) !== -1 || used.includes(fileName)) { + fileName = `${originalName} (${i++})`; + if (dotIndex !== -1) fileName += extension; + } + + props.author = "ไม่พบข้อมูล"; + + used.push(fileName); + + return { fileName: fileName, ...props }; + }) + : body.fileList; + + const map = fileList.map(async ({ fileName, ...props }) => [ + fileName, + await createFile(path, fileName, props), + ]); + + const result = await Promise.all(map).catch((e) => + console.error(`Storage Service Error: ${e}`), + ); + + if (!result || result.some((v) => !v[1])) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถอัปโหลดไฟล์ได้"); + } + + return Object.fromEntries(result); + } + /** + * @example id "00000000-0000-0000-0000-000000000000" + * @example file "เอกสาร 1.docx" + * + * @summary แก้ไขข้อมูลไฟล์ของ id นั้นๆ + */ + @Patch("{id}/{file}") + public async updateFile( + @Path() id: string, + @Path() file: string, + @Body() + body: { + title?: string; + description?: string; + keyword?: string[]; + category?: string[]; + author?: string; + metadata?: { [key: string]: unknown }; + }, + ) { + const props = body; + const result = await updateFile( + ["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน", id], + file, + props, + ); + + if (!result) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถแก้ไขไฟล์ได้"); + } + return this.setStatus(HttpStatus.NO_CONTENT); + } + + /** + * @example id "00000000-0000-0000-0000-000000000000" + * + * @summary ลบไฟล์ทั้งหมดของ id นั้นๆ + */ + @Delete("{id}") + public async deleteFolder(@Path() id: string) { + const result = await deleteFolder(["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน"], id); + + if (!result) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบแฟ้มได้"); + } + return this.setStatus(HttpStatus.NO_CONTENT); + } + + /** + * @example id "00000000-0000-0000-0000-000000000000" + * @example file "เอกสาร 1.docx" + * + * @summary ลบไฟล์ของ id นั้นๆ + */ + @Delete("{id}/{file}") + public async deleteFile(@Path() id: string, @Path() file: string) { + const result = await deleteFile(["ระบบเงินเดือน", "เอกสารอ้างอิงผังเงินเดือน", id], file); + + if (!result) { + throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบไฟล์ ได้"); + } + return this.setStatus(HttpStatus.NO_CONTENT); + } +}