From 90e1050ad7cc05f160a62fd5b419ddb707da5f2b Mon Sep 17 00:00:00 2001 From: Bright Date: Thu, 15 Feb 2024 15:38:04 +0700 Subject: [PATCH 1/5] =?UTF-8?q?API=20=E0=B8=AA=E0=B8=A3=E0=B9=89=E0=B8=B2?= =?UTF-8?q?=E0=B8=87=E0=B8=9C=E0=B8=B1=E0=B8=87=E0=B9=80=E0=B8=87=E0=B8=B4?= =?UTF-8?q?=E0=B8=99=E0=B9=80=E0=B8=94=E0=B8=B7=E0=B8=AD=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/SalaryController.ts | 107 ++++++++++++++++++++++++++++ src/entities/Salarys.ts | 8 +-- 2 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 src/controllers/SalaryController.ts diff --git a/src/controllers/SalaryController.ts b/src/controllers/SalaryController.ts new file mode 100644 index 0000000..0a81924 --- /dev/null +++ b/src/controllers/SalaryController.ts @@ -0,0 +1,107 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Patch, + Route, + Security, + Tags, + Body, + Path, + Request, + Example, +} from "tsoa"; +import { Salarys, CreateSalary, UpdateSalary } from "../entities/Salarys"; +import { PosType } from "../entities/PosType"; +import { PosLevel } from "../entities/PosLevel"; +import { AppDataSource } from "../database/data-source"; +import { In, IsNull, Not } from "typeorm"; +import HttpSuccess from "../interfaces/http-success"; +import HttpError from "../interfaces/http-error"; +import HttpStatusCode from "../interfaces/http-status"; + +@Route("api/v1/salary") +@Tags("Salary") +@Security("bearerAuth") +export class Salary extends Controller { + + private salaryRepository = AppDataSource.getRepository(Salarys); + private poTypeRepository = AppDataSource.getRepository(PosType); + private posLevelRepository = AppDataSource.getRepository(PosLevel); + + /** + * API สร้างผังเงินเดือน + * + * @summary SLR_001 - สร้างผังเงินเดือน #1 + * + */ + @Post() + @Example( + { + salaryType: "string", //*ประเภทผัง (OFFICER->"ข้าราชการกรุงเทพมหานครสามัญ",EMPLOYEE->"ลูกจ้างประจำกรุงเทพมหานคร") + posType: "string", //*ระดับของตำแหน่ง + posLevel: "string", //*ประเภทของตำแหน่ง + isActive: "boolean", //*สถานะการใช้งาน + date: "datetime", //ให้ไว้ ณ วันที่ + startDate: "datetime", //วันที่มีผลบังคับใช้ + endDate: "datetime", //วันที่สิ้นสุดบังคับใช้ + detail: "string", //คำอธิบาย + } + ) + async create( + @Body() requestBody: CreateSalary, + @Request() request: { user: Record }, + ){ + const salarys = Object.assign(new Salarys(), requestBody); + if (!salarys) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); + } + + const chk_salaryType = ["OFFICER", "EMPLOYEE"]; + if (!chk_salaryType.includes(salarys.salaryType.toUpperCase())) { + throw new HttpError(HttpStatusCode.NOT_FOUND, "ประเภทผัง ไม่ถูกต้อง"); + } + + const chk_posTypeId = await this.poTypeRepository.findOne({ + where: { id: salarys.posTypeId } + }) + if(!chk_posTypeId){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ประเภทของตำแหน่ง ไม่ถูกต้อง"); + } + + const chk_posLevelId = await this.posLevelRepository.findOne({ + where: { id: salarys.posLevelId } + }); + if(!chk_posLevelId){ + throw new HttpError(HttpStatusCode.NOT_FOUND, "ระดับของตำแหน่ง ไม่ถูกต้อง"); + } + + const chk_3fields = await this.salaryRepository.findOne({ + where: { + salaryType: salarys.salaryType, + posTypeId: salarys.posTypeId, + posLevelId: salarys.posLevelId + } + }) + if(chk_3fields && salarys.isActive){ + salarys.isActive = false; + } + + try { + salarys.salaryType = salarys.salaryType.toUpperCase(); + salarys.createdUserId = request.user.sub; + salarys.createdFullName = request.user.name; + salarys.lastUpdateUserId = request.user.sub; + salarys.lastUpdateFullName = request.user.name; + await this.salaryRepository.save(salarys); + return new HttpSuccess(salarys.id); + } catch (error) { + return error; + } + } + +} + + diff --git a/src/entities/Salarys.ts b/src/entities/Salarys.ts index 2a1483a..973af30 100644 --- a/src/entities/Salarys.ts +++ b/src/entities/Salarys.ts @@ -14,13 +14,13 @@ export class Salarys extends EntityBase { @Column({ length: 40, - comment: "Id ระดับของตำแหน่ง", + comment: "Id ประเภทของตำแหน่ง", }) posTypeId: string; @Column({ length: 40, - comment: "Id ประเภทของตำแหน่ง", + comment: "Id ระดับของตำแหน่ง", }) posLevelId: string; @@ -73,7 +73,7 @@ export class Salarys extends EntityBase { posLevel_: PosLevel; } -export class CreateSalaryRank { +export class CreateSalary { @Column() salaryType: string; @@ -101,4 +101,4 @@ export class CreateSalaryRank { } -export type UpdateSalaryRank = Partial ; \ No newline at end of file +export type UpdateSalary = Partial ; \ No newline at end of file From 0ae376ca4b6bfa59e22481efc2b1f4c96174eb69 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:11:01 +0700 Subject: [PATCH 2/5] Add EDM service --- src/interfaces/storage-fs.ts | 39 +++++++ src/services/edm.ts | 219 +++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 src/interfaces/storage-fs.ts create mode 100644 src/services/edm.ts diff --git a/src/interfaces/storage-fs.ts b/src/interfaces/storage-fs.ts new file mode 100644 index 0000000..63d0769 --- /dev/null +++ b/src/interfaces/storage-fs.ts @@ -0,0 +1,39 @@ +export interface StorageFolder { + /** + * @prop Full path to this folder. It is used as key as there are no files or directories at the same location. + */ + pathname: string; + /** + * @prop Directory / Folder name. + */ + name: string; + + createdAt: string | Date; + createdBy: string | Date; +} + +export interface StorageFile { + /** + * @prop Full path to this folder. It is used as key as there are no files or directories at the same location. + */ + pathname: string; + + fileName: string; + fileSize: number; + fileType: string; + + title: string; + description: string; + author: string; + category: string[]; + keyword: string[]; + metadata: Record; + + path: string; + upload: boolean; + + updatedAt: string | Date; + updatedBy: string; + createdAt: string | Date; + createdBy: string; +} diff --git a/src/services/edm.ts b/src/services/edm.ts new file mode 100644 index 0000000..176c4da --- /dev/null +++ b/src/services/edm.ts @@ -0,0 +1,219 @@ +import { DecodedJwt, createDecoder } from "fast-jwt"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; +import { StorageFile, StorageFolder } from "../interfaces/storage-fs"; + +const jwtDecode = createDecoder({ complete: true }); + +if (!process.env.STORAGE_URL) { + throw new Error("Requires STORAGE_URL env variable."); +} + +if (!process.env.STORAGE_REALM_URL && !process.env.STORAGE_SECRET) { + throw new Error("Requires STORAGE_REALM_URL and STORAGE_SECRET env variable."); +} + +export type FileProps = Partial< + Pick +> & { + metadata?: { [key: string]: unknown }; +}; + +const STORAGE_URL = process.env.STORAGE_URL; +const STORAGE_REALM_URL = process.env.STORAGE_REALM_URL!; +const STORAGE_SECRET = process.env.STORAGE_SECRET!; + +let token: string | null = null; +let decoded: DecodedJwt | null = null; + +/** + * Check if token is expired or will expire in 30 seconds + * @returns true if expire or can't get exp, false otherwise + */ +export function expireCheck(token: string, beforeExpire: number = 30) { + decoded = jwtDecode(token); + + if (decoded && decoded.payload.exp) { + return Date.now() / 1000 >= decoded.payload.exp - beforeExpire; + } + return true; +} + +/** + * Get token from id service if needed + */ +export async function getToken() { + if (!token || expireCheck(token)) { + const body = new URLSearchParams(); + + body.append("client_id", "ext-api"); + body.append("client_secret", STORAGE_SECRET); + body.append("grant_type", "client_credentials"); + + const res = await fetch(`${STORAGE_REALM_URL}/protocol/openid-connect/token`, { + method: "POST", + body: body, + }).catch((e) => console.error(e)); + + if (!res) return; + + const data = await res.json(); + + if (data && data.access_token) { + token = data.access_token; + } + } + return token; +} + +/** + * @param path - Path that new folder will live + * @param name - Name of the folder to create + * @param recursive - Will create parent automatically + */ +export async function createFolder(path: string[], name: string, recursive: boolean = false) { + if (recursive && path.length > 0) { + await createFolder(path.slice(0, -1), path[path.length - 1], true); + } + + const res = await fetch(`${STORAGE_URL}/storage/folder`, { + method: "POST", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ path, name }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + return Boolean(console.error(res ? await res.json() : res)); + } + return true; +} + +/** + * @param path - Path that new file will live + * @param file - Name of the file to create + */ +export async function createFile(path: string[], file: string, props?: FileProps) { + const res = await fetch(`${STORAGE_URL}/storage/file`, { + method: "POST", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...props, + path, + file, + hidden: false, + }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + return Boolean(console.error(res ? await res.json() : res)); + } + return (await res.json()) as StorageFile & { uploadUrl: string }; +} + +export async function list(operation: "file" | "folder", path: string[]) { + const res = await fetch(`${STORAGE_URL}/storage/list`, { + method: "POST", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ operation, path, hidden: false }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + if (res && res.status === HttpStatus.NOT_FOUND) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบแฟ้ม/ไฟล์ในระบบ"); + } + return Boolean(console.error(res ? await res.json() : res)); + } + return (await res.json()) as StorageFile & { uploadUrl: string }; +} + +export async function listFolder(path: string[]) { + return (await list("folder", path)) as StorageFolder[] | boolean; +} + +export async function listFile(path: string[]) { + return (await list("file", path)) as StorageFile[] | boolean; +} + +export async function updateFile(path: string[], file: string, metadata: FileProps) { + const res = await fetch(`${STORAGE_URL}/storage/file`, { + method: "PUT", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + from: { path, file }, + ...metadata, + upload: false, + }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + if (res && res.status === HttpStatus.NOT_FOUND) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบไฟล์ในระบบ"); + } + return Boolean(console.error(res ? await res.json() : res)); + } + + return Boolean(res); +} + +export async function deleteFolder(path: string[], name: string) { + const res = await fetch(`${STORAGE_URL}/storage/folder`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ path: [...path, name] }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + return Boolean(console.error(res ? await res.json() : res)); + } + return true; +} + +export async function deleteFile(path: string[], file: string) { + const res = await fetch(`${STORAGE_URL}/storage/file`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ path, file }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + return Boolean(console.error(res ? await res.json() : res)); + } + return true; +} + +export async function downloadFile(path: string[], file: string) { + const res = await fetch(`${STORAGE_URL}/storage/file/download`, { + method: "POST", + headers: { + Authorization: `Bearer ${await getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ path, file }), + }).catch((e) => console.error(e)); + + if (!res || !res.ok) { + if (res && res.status === HttpStatus.NOT_FOUND) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบไฟล์ในระบบ"); + } + return Boolean(console.error(res ? await res.json() : res)); + } + return (await res.json()) as StorageFile & { downloadUrl: string }; +} From b9c9f558559cfcff6b5cd9a3fa6b8e28f29b5ebd Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:21:14 +0700 Subject: [PATCH 3/5] Add file endpoint --- src/controllers/StorageController.ts | 278 +++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 src/controllers/StorageController.ts 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); + } +} From 21fb1d5f093cf935098116696e3cadcef8928fa9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:21:22 +0700 Subject: [PATCH 4/5] Protect endpoint --- src/controllers/StorageController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index a383bf3..82cad9b 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -8,6 +8,7 @@ import { Path, Post, Route, + Security, SuccessResponse, Tags, } from "tsoa"; @@ -23,6 +24,7 @@ import { } from "../services/edm"; @Route("api/v1/salary/file") +@Security("bearerAuth") @Tags("Document") export class DocumentController extends Controller { /** From 4f5230266251130fe1af9899a810f7916b05922a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:23:42 +0700 Subject: [PATCH 5/5] Reference issue / feat --- src/controllers/StorageController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index 82cad9b..1c93e08 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -30,7 +30,7 @@ export class DocumentController extends Controller { /** * @example id "00000000-0000-0000-0000-000000000000" * - * @summary ข้อมูลเอกสารทั้ง + * @summary SLR_015 - รายการเอกสารอ้างอิงผังเงินเดือน */ @Get("{id}") @SuccessResponse(200, "สำเร็จ") @@ -109,7 +109,7 @@ export class DocumentController extends Controller { * * @example id "00000000-0000-0000-0000-000000000000" * - * @summary ร้องขอการอัปโหลดเอกสาร + * @summary SLR_013 - เพิ่มเอกสารอ้างอิงผังเงินเดือน */ @Post("{id}") @Example([ @@ -266,7 +266,7 @@ export class DocumentController extends Controller { * @example id "00000000-0000-0000-0000-000000000000" * @example file "เอกสาร 1.docx" * - * @summary ลบไฟล์ของ id นั้นๆ + * @summary SLR_014 - ลบเอกสารอ้างอิงผังเงินเดือน */ @Delete("{id}/{file}") public async deleteFile(@Path() id: string, @Path() file: string) {