import { Body, Controller, Delete, Example, Get, Patch, Path, Post, Route, Security, SuccessResponse, Tags, } from "tsoa"; import HttpStatus from "../interfaces/http-status"; import { createFile, createFolder, deleteFile, deleteFolder, downloadFile, listFile, updateFile, } from "../services/edm"; import { s3DownloadFile, s3ListFile, s3UploadFile } from "../services/minio"; @Route("api/v1/salary/file/{name}/{group}") @Security("bearerAuth") @Tags("File") export class FileController extends Controller { /** * * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" */ @Get("{id}") @SuccessResponse(200, "สำเร็จ") @Example([ { pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/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() name: string, @Path() group: string, @Path() id: string) { if (name !== "ระบบประเมิน") { return await s3ListFile(name, group, id).catch((e) => { console.error(e); throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); }); } const list = await listFile([name, group, id]); if (!list) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); return list; } /** * ข้อควรระวัง: หากลิงก์ยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ * * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * * @summary ข้อมูลเอกสารพร้อมลิงก์ดาวน์โหลด */ @Get("/{id}/{file}") @SuccessResponse(200, "สำเร็จ") @Example({ pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/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() name: string, @Path() group: string, @Path() id: string, @Path() file: string, ) { if (name !== "ระบบประเมิน") { return await s3DownloadFile(name, group, id, file); } const data = await downloadFile([name, group, 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 name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" */ @Post("{id}") @Example([ { pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/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() name: string, @Path() group: string, @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 = [name, group, id]; if (name !== "ระบบประเมิน") { const ret = await Promise.all( body.fileList.map(async ({ fileName }) => [ fileName, await s3UploadFile(!!body.replace, ...path, fileName), ]), ); return Object.fromEntries(ret); } if (!(await createFolder(path.slice(0, -1), id, true))) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); } const list = await listFile(path); if (!list || !Array.isArray(list)) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ เข้าถึงรายการไฟล์ได้"); } let fileList = !body.replace ? await Promise.all( body.fileList.map(async ({ 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 copyFileName = fileName; while (list.findIndex((v) => v.fileName === fileName) !== -1) { let copy = 0; while ( list.findIndex( (v) => v.fileName === `${originalName} (${copy + 1})` + (dotIndex !== -1 ? extension : ""), ) !== -1 ) { copy++; } copyFileName = `${originalName} (${copy + 1})` + extension; const result = await updateFile( path, fileName, { ...props, title: copyFileName }, path, copyFileName, ); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถแก้ไขไฟล์ได้"); } break; } props.author = "ไม่พบข้อมูล"; 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 name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * * @summary แก้ไขข้อมูลไฟล์ของ id นั้นๆ */ @Patch("{id}/{file}") public async updateFile( @Path() name: string, @Path() group: string, @Path() id: string, @Path() file: string, @Body() body: { title?: string; description?: string; keyword?: string[]; category?: string[]; author?: string; metadata?: { [key: string]: unknown }; }, ) { if (name !== "ระบบประเมิน") return; // do nothing, no metadata to update const props = body; const result = await updateFile([name, group, id], file, props); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถแก้ไขไฟล์ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } /** * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * * @summary ลบไฟล์ทั้งหมดของ id นั้นๆ */ @Delete("{id}") public async deleteFolder(@Path() name: string, @Path() group: string, @Path() id: string) { const result = await deleteFolder([name, group], id); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบแฟ้มได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } /** * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * * @summary SLR_014 - ลบเอกสารอ้างอิงผังเงินเดือน */ @Delete("{id}/{file}") public async deleteFile( @Path() name: string, @Path() group: string, @Path() id: string, @Path() file: string, ) { const result = await deleteFile([name, group, id], file); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบไฟล์ ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } } @Route("api/v1/salary/sub-file/{name}/{group}/{id}") @Security("bearerAuth") @Tags("File") export class SubFileController extends Controller { /** * * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" */ @Get("{subId}") @SuccessResponse(200, "สำเร็จ") @Example([ { pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/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() name: string, @Path() group: string, @Path() id: string, @Path() subId: string, ) { if (name !== "ระบบประเมิน") { return await s3ListFile(name, group, id, subId).catch((e) => { console.error(e); throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); }); } // if (name !== "ระบบประเมิน") { // return await s3ListFile(name, group, id); // } const list = await listFile([name, group, id, subId]); if (!list) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); return list; } /** * ข้อควรระวัง: หากลิงก์ยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ * * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * * @summary ข้อมูลเอกสารพร้อมลิงก์ดาวน์โหลด */ @Get("{subId}/{file}") @SuccessResponse(200, "สำเร็จ") @Example({ pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/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() name: string, @Path() group: string, @Path() id: string, @Path() subId: string, @Path() file: string, ) { if (name !== "ระบบประเมิน") { return await s3DownloadFile(name, group, id, subId); } const data = await downloadFile([name, group, id, subId], 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 name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" */ @Post("{subId}") @Example([ { pathname: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/เอกสาร 1.docx", path: "ระบบประเมิน/เล่ม 1/00000000-0000-0000-0000-000000000000/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() name: string, @Path() group: string, @Path() id: string, @Path() subId: 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 = [name, group, id, subId]; if (name !== "ระบบประเมิน") { const ret = await Promise.all( body.fileList.map(async ({ fileName }) => [ fileName, await s3UploadFile(!!body.replace, ...path, fileName), ]), ); return Object.fromEntries(ret); } if (!(await createFolder(path.slice(0, -1), subId, true))) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); } const list = await listFile(path); if (!list || !Array.isArray(list)) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ เข้าถึงรายการไฟล์ได้"); } let fileList = !body.replace ? await Promise.all( body.fileList.map(async ({ 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 copyFileName = fileName; while (list.findIndex((v) => v.fileName === fileName) !== -1) { let copy = 0; while ( list.findIndex( (v) => v.fileName === `${originalName} (${copy + 1})` + (dotIndex !== -1 ? extension : ""), ) !== -1 ) { copy++; } copyFileName = `${originalName} (${copy + 1})` + extension; const result = await updateFile( path, fileName, { ...props, title: copyFileName }, path, copyFileName, ); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถแก้ไขไฟล์ได้"); } break; } props.author = "ไม่พบข้อมูล"; 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 name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * * @summary แก้ไขข้อมูลไฟล์ของ id นั้นๆ */ @Patch("{subId}/{file}") public async updateFile( @Path() name: string, @Path() group: string, @Path() id: string, @Path() subId: string, @Path() file: string, @Body() body: { title?: string; description?: string; keyword?: string[]; category?: string[]; author?: string; metadata?: { [key: string]: unknown }; }, ) { if (name !== "ระบบประเมิน") return; // do nothing, no metadata to update const props = body; const result = await updateFile([name, group, id, subId], file, props); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถแก้ไขไฟล์ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } /** * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" * * @summary ลบไฟล์ทั้งหมดของ id นั้นๆ */ @Delete("{subId}") public async deleteFolder( @Path() name: string, @Path() group: string, @Path() id: string, @Path() subId: string, ) { const result = await deleteFolder([name, group, id], subId); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบแฟ้มได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } /** * @example name "ระบบประเมิน" * @example group "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example subId "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" */ @Delete("{subId}/{file}") public async deleteFile( @Path() name: string, @Path() group: string, @Path() id: string, @Path() subId: string, @Path() file: string, ) { const result = await deleteFile([name, group, id, subId], file); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถลบไฟล์ ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } }