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/storage"; import { AppDataSource } from "../database/data-source"; import { Evaluation } from "../entities/Evaluation"; import HttpError from "../interfaces/http-error"; import HttpStatusCode from "../interfaces/http-status"; @Route("api/v1/evaluation/document") @Tags("document") export class DocumentController extends Controller { /** * @example volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * * @summary ข้อมูลเอกสารทั้งชุด */ @Get("{volume}/{id}") @SuccessResponse(200, "สำเร็จ") @Example([ { pathname: "string", path: "ระบบประเมิน/เล่ม 1/1-แบบพิจารณาคุณสมบัติบุคคล", title: "1-แบบพิจารณาคุณสมบัติบุคคล.docx", description: "", author: "นายก", metadata: { tag: 1, }, keyword: [], category: [], fileType: "", fileSize: 1024, fileName: "1-แบบพิจารณาคุณสมบัติบุคคล.docx", 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() volume: string, @Path() id: string) { try { const list = await listFile(["ระบบประเมิน", volume, id]); if (!list) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); return list; } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } /** * ข้อควรระวัง: หากลิงก์ยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ * * @example volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "1-แบบพิจารณาคุณสมบัติบุคคล" * * @summary ข้อมูลเอกสารพร้อมลิงก์ดาวน์โหลด */ @Get("{volume}/{id}/{file}") @SuccessResponse(200, "สำเร็จ") @Example({ pathname: "string", path: "ระบบประเมิน/เล่ม 1/1-แบบพิจารณาคุณสมบัติบุคคล", title: "1-แบบพิจารณาคุณสมบัติบุคคล.docx", 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() volume: string, @Path() file: string) { try { const data = await downloadFile(["ระบบประเมิน", volume, id], file); if (!data) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); return data; } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } /** * ข้อควรระวัง: หากลิงก์ภาษาไทยยาวเกินไปอาจทำให้ไม่สามารถอัปโหลดได้ (น่าจะเป็นปัญหาทางด้านเทคนิคของ 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 volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * * @summary ร้องขอการอัปโหลดเอกสาร */ @Post("{volume}/{id}") @Example([ { pathname: "string", path: "ระบบประเมิน/เล่ม 1/1-แบบพิจารณาคุณสมบัติบุคคล", title: "1-แบบพิจารณาคุณสมบัติบุคคล.docx", description: "1-แบบพิจารณาคุณสมบัติบุคคล.docx", 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() volume: string, @Path() id: string, @Body() body: { /** * @example [ * { * "fileName": "1-แบบพิจารณาคุณสมบัติบุคคล.docx", * "title": "1-แบบพิจารณาคุณสมบัติบุคคล.docx" * }, * { * "fileName": "2-แบบแสดงรายละเอียดการเสนอผลงาน.docx", * "description": "2-แบบแสดงรายละเอียดการเสนอผลงาน.docx" * }, * { * "fileName": "3-แบบตรวจสอบความถูกต้องครบถ้วนของข้อมูล.docx", * "keyword": ["3-แบบตรวจสอบความถูกต้องครบถ้วนของข้อมูล.docx"] * }, * { * "fileName": "4-แบบประเมินคุณลักษณะบุคคล.docx", * "category": ["4-แบบประเมินคุณลักษณะบุคคล.docx"] * }, * { * "fileName": "5-แบบสรุปข้อมูลของผู้ขอรับการคัดเลือก.docx", * "author": "นายก" * }, * { * "fileName": "6-ผลงานที่จะส่งประเมิน.docx", * "metadata": { "tag1": "value1", "tag2": "value2" } * } * ] */ fileList: { fileName: string; title?: string; description?: string; keyword?: string[]; category?: string[]; author?: string; metadata?: { [key: string]: unknown; }; }[]; /** * @example false */ replace?: boolean; }, ) { try { const path = ["ระบบประเมิน", volume]; if (!(await createFolder(path, id, true))) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); } const list = await listFile(["ระบบประเมิน", volume, id]); if (!list || !Array.isArray(list)) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถสร้างแฟ้มได้"); } let used: string[] = []; const evaluation = AppDataSource.getRepository(Evaluation); let author = await evaluation.findOne({ where: { id }, }); 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; } if (author) { props.author = `${author.prefix}${author.fullName}`; } else { props.author = "ไม่พบข้อมูล"; } used.push(fileName); return { fileName: fileName, ...props }; }) : body.fileList; const map = fileList.map(async ({ fileName, ...props }) => [ fileName, await createFile([...path, id], 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); } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } /** * @example volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "1-แบบพิจารณาคุณสมบัติบุคคล.docx" * * @summary แก้ไขข้อมูลไฟล์ของ id นั้นๆ */ @Patch("{volume}/{id}/{file}") public async updateFile( @Path() volume: string, @Path() id: string, @Path() file: string, @Body() body: { title?: string; description?: string; keyword?: string[]; category?: string[]; author?: string; metadata?: { [key: string]: unknown }; }, ) { try { const props = body; const result = await updateFile(["ระบบประเมิน", volume, id], file, props); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถอัปโหลดไฟล์ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } /** * @example volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * * @summary ลบไฟล์ทั้งหมดของ id นั้นๆ */ @Delete("{volume}/{id}") public async deleteFolder(@Path() volume: string, @Path() id: string) { try { const result = await deleteFolder(["ระบบประเมิน", volume], id); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถอัปโหลดไฟล์ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } /** * @example volume "เล่ม 1" * @example id "00000000-0000-0000-0000-000000000000" * @example file "1-แบบพิจารณาคุณสมบัติบุคคล.docx" * * @summary ลบไฟล์ของ id นั้นๆ */ @Delete("{volume}/{id}/{file}") public async deleteFile(@Path() volume: string, @Path() id: string, @Path() file: string) { try { const result = await deleteFile(["ระบบประเมิน", volume, id], file); if (!result) { throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์ ไม่สามารถอัปโหลดไฟล์ได้"); } return this.setStatus(HttpStatus.NO_CONTENT); } catch (error: any) { if (error instanceof HttpError) { throw error; } else throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, error); } } }