804 lines
27 KiB
TypeScript
804 lines
27 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Example,
|
|
Post,
|
|
Put,
|
|
Request,
|
|
Response,
|
|
Route,
|
|
Security,
|
|
SuccessResponse,
|
|
Tags,
|
|
} from "tsoa";
|
|
|
|
import minioClient from "../minio";
|
|
import esClient from "../elasticsearch";
|
|
|
|
import HttpError from "../interfaces/http-error";
|
|
import HttpStatusCode from "../interfaces/http-status";
|
|
import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
|
|
|
|
import { copyCond } from "../utils/minio";
|
|
|
|
import * as io from "../lib/websocket";
|
|
|
|
if (!process.env.MINIO_BUCKET) throw Error("Default MinIO bucket must be specified.");
|
|
if (!process.env.ELASTICSEARCH_INDEX) throw Error("Default ElasticSearch index must be specified.");
|
|
|
|
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
|
|
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
|
|
const MINIO_ERROR_MESSAGE = "เกิดข้อผิดพลาดกับระบบจัดการไฟล์";
|
|
const PATH_NOT_FOUND_MESSAGE = "ไม่พบตำแหน่งที่ต้องการนำข้อมูลเข้า";
|
|
const PATH_ALREADY_EXIST = "ตำแหน่งดังกล่าวมีในระบบแล้ว";
|
|
|
|
interface ListRequestBody {
|
|
operation: "folder" | "file";
|
|
path: string[];
|
|
hidden?: boolean;
|
|
}
|
|
|
|
interface FolderBody {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2"] */
|
|
path: string[];
|
|
/** @example "แฟ้ม 3" */
|
|
name: string;
|
|
}
|
|
|
|
interface PutFolderBody {
|
|
from: {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2"] */
|
|
path: string[];
|
|
/** @example "แฟ้ม 3" */
|
|
name: string;
|
|
};
|
|
to: {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2"] */
|
|
path: string[];
|
|
/** @example "แฟ้ม 3 แก้ไข" */
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
interface DeleteFolderBody {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3 แก้ไข"] */
|
|
path: string[];
|
|
}
|
|
|
|
interface FileBody {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3"] */
|
|
path: string[];
|
|
/** @example "ไฟล์ 1.xlsx" */
|
|
file: string;
|
|
/** @example "การเงิน" */
|
|
title?: string;
|
|
/** @example "การเงิน" */
|
|
description?: string;
|
|
/** @example ["การเงิน", "รายงาน"] */
|
|
category?: string[];
|
|
/** @example ["การเงิน", "รายรับ", "รายจ่าย"] */
|
|
keyword?: string[];
|
|
/** @example false */
|
|
hidden?: boolean;
|
|
}
|
|
|
|
interface PutFileBody extends Omit<FileBody, "file" | "path"> {
|
|
/** หากต้องการอัพโหลดไฟล์ด้วยให้ส่งค่าเป็นจริง */
|
|
from: {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3"] */
|
|
path: string[];
|
|
/** @example "ไฟล์ 1.xlsx" */
|
|
file: string;
|
|
};
|
|
to?: {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3"] */
|
|
path: string[];
|
|
/** @example "ไฟล์ 1 แก้ไข.xlsx" */
|
|
file: string;
|
|
};
|
|
/** @example false */
|
|
upload?: boolean;
|
|
}
|
|
|
|
interface DeleteFileBody {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3"] */
|
|
path: string[];
|
|
/** @example "ไฟล์ 1 แก้ไข.xlsx" */
|
|
file: string;
|
|
}
|
|
|
|
interface DownloadFileBody {
|
|
/** @example ["แฟ้ม 1", "แฟ้ม 2", "แฟ้ม 3"] */
|
|
path: string[];
|
|
/** @example "ไฟล์ 1 แก้ไข.xlsx" */
|
|
file: string;
|
|
}
|
|
|
|
function stripLeadingSlash(str: string) {
|
|
return str.replace(/^\//, "");
|
|
}
|
|
|
|
async function folderSize(path: string[]) {
|
|
const size = await new Promise<number>((resolve, reject) => {
|
|
const stream = minioClient.listObjectsV2(
|
|
DEFAULT_BUCKET,
|
|
path.length === 0 ? "" : path.join("/") + "/",
|
|
true,
|
|
);
|
|
let total: number = 0;
|
|
|
|
stream.on("data", (v) => {
|
|
if (v && v.size) total += v.size;
|
|
});
|
|
stream.on("end", () => resolve(total));
|
|
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์")));
|
|
});
|
|
return size;
|
|
}
|
|
|
|
async function listFolder(path: string[], hidden: boolean = false) {
|
|
const list = await new Promise<{ pathname: string; name: string }[]>((resolve, reject) => {
|
|
const item: { pathname: string; name: string }[] = [];
|
|
|
|
const stream = minioClient.listObjectsV2(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${path.join("/")}/`),
|
|
);
|
|
stream.on("data", (v) => {
|
|
if (v && v.prefix)
|
|
item.push({
|
|
pathname: v.prefix,
|
|
name: v.prefix.split("/").filter(Boolean).at(-1)!,
|
|
});
|
|
});
|
|
stream.on("end", () => resolve(item));
|
|
stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE)));
|
|
});
|
|
|
|
const folder = await Promise.all(
|
|
list.map(async (v) => {
|
|
if (v.name.startsWith(".") && !hidden) return undefined;
|
|
|
|
// Get stat from hidden object that used to mark as folder as minio doesn't really have folder
|
|
const stat = await minioClient
|
|
.statObject(DEFAULT_BUCKET, `${v.pathname}.keep`)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
|
|
if (!stat) return undefined;
|
|
|
|
const { createdat, createdby } = stat.metaData;
|
|
|
|
return {
|
|
...v,
|
|
createdAt: createdat ?? "n/a",
|
|
createdBy: createdby ?? "n/a",
|
|
} satisfies StorageFolder;
|
|
}),
|
|
);
|
|
|
|
return folder.filter((v: StorageFolder | undefined): v is StorageFolder => !!v);
|
|
}
|
|
|
|
async function listFile(path: string[], hidden: boolean = false) {
|
|
const result = await esClient
|
|
.search<StorageFile & { attachment: Record<string, string> }>({
|
|
index: DEFAULT_INDEX,
|
|
sort: [{ pathname: "asc" }],
|
|
query: {
|
|
bool: {
|
|
must: { match: { path: stripLeadingSlash(`${path.join("/")}/`) } },
|
|
must_not: !hidden ? { match: { hidden: true } } : undefined,
|
|
},
|
|
},
|
|
size: 10000,
|
|
})
|
|
.then((r) => r.hits.hits);
|
|
|
|
const records = result
|
|
.map((v) => {
|
|
if (v._source) {
|
|
const { attachment, ...rest } = v._source;
|
|
return rest satisfies StorageFile;
|
|
}
|
|
})
|
|
.filter((v: StorageFile | undefined): v is StorageFile => !!v);
|
|
|
|
return records;
|
|
}
|
|
|
|
async function checkPathExist(bucket: string, path: string[]) {
|
|
if (path.filter(Boolean).length === 0) return true; // root does not contain any mark
|
|
return await checkFileExist(bucket, `${path.filter(Boolean).join("/")}/.keep`);
|
|
}
|
|
|
|
async function checkFileExist(bucket: string, pathname: string) {
|
|
return Boolean(
|
|
await minioClient.statObject(bucket, stripLeadingSlash(pathname)).catch((e) => {
|
|
if (e.code === "NotFound") return false;
|
|
console.error(`Storage Error: ${e}`);
|
|
throw new Error(MINIO_ERROR_MESSAGE);
|
|
}),
|
|
);
|
|
}
|
|
|
|
@Route("storage")
|
|
export class StorageController extends Controller {
|
|
/**
|
|
* @summary แสดงรายการแฟ้มหรือไฟล์
|
|
*/
|
|
@Post("list")
|
|
@Tags("Storage Folder", "Storage File")
|
|
@Security("bearerAuth")
|
|
@Example([
|
|
{
|
|
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
|
|
name: "แฟ้ม 1",
|
|
createdAt: "2021-07-20T12:33:13.018Z",
|
|
createdBy: "admin",
|
|
},
|
|
{
|
|
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2/",
|
|
name: "แฟ้ม 2",
|
|
createdAt: "2022-01-23T16:05:02.114Z",
|
|
createdBy: "admin",
|
|
},
|
|
])
|
|
@Example([
|
|
{
|
|
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1.pdf",
|
|
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
|
|
title: "เอกสาร",
|
|
description: "เอกสารการเงิน",
|
|
category: ["บัญชี"],
|
|
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
|
|
upload: false,
|
|
hidden: false,
|
|
fileName: "เอกสาร 1.pdf",
|
|
fileSize: 10240,
|
|
fileType: "application/pdf",
|
|
createdAt: "2021-07-20T12:33:13.018Z",
|
|
createdBy: "admin",
|
|
updatedAt: "2021-07-20T12:33:13.018Z",
|
|
updatedBy: "admin",
|
|
},
|
|
])
|
|
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
|
public async getList(@Body() body: ListRequestBody) {
|
|
const path = body.path.filter(Boolean);
|
|
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
|
}
|
|
|
|
if (body.operation === "folder") return await listFolder(path, body.hidden);
|
|
if (body.operation === "file") return await listFile(path, body.hidden);
|
|
}
|
|
|
|
/**
|
|
* @summary สร้างแฟ้ม
|
|
*/
|
|
@Post("folder")
|
|
@Tags("Storage Folder")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
|
public async postFolder(
|
|
@Request() request: { user: { preferred_username: string } },
|
|
@Body() body: FolderBody,
|
|
) {
|
|
const { path, name } = body;
|
|
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
|
}
|
|
|
|
const meta = {
|
|
createdAt: new Date().toISOString(),
|
|
createdBy: request.user.preferred_username,
|
|
};
|
|
|
|
const created = await minioClient
|
|
.putObject(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`),
|
|
"",
|
|
0,
|
|
meta,
|
|
)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
|
|
if (!created) throw new Error(MINIO_ERROR_MESSAGE);
|
|
|
|
!name.startsWith(".") &&
|
|
io.getInstance()?.emit("FolderCreate", {
|
|
pathname: stripLeadingSlash(
|
|
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
|
|
),
|
|
name: name.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
|
|
...meta,
|
|
});
|
|
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* ย้ายแฟ้มภายในตำแหน่งหนึ่งไปภายในอีกแฟ้มหนึ่ง
|
|
* หรือเปลี่ยนชื่อได้
|
|
*
|
|
* @summary ย้ายแฟ้ม
|
|
*/
|
|
@Put("folder")
|
|
@Tags("Storage Folder")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
|
public async moveFolder(@Body() body: PutFolderBody) {
|
|
const src = stripLeadingSlash(`${body.from.path.join("/")}/${body.from.name}`);
|
|
const dst = stripLeadingSlash(`${body.to.path.join("/")}/${body.to.name}`);
|
|
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, src.split("/")))) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
|
}
|
|
if (await checkPathExist(DEFAULT_BUCKET, dst.split("/"))) {
|
|
throw new HttpError(HttpStatusCode.CONFLICT, PATH_ALREADY_EXIST);
|
|
}
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
|
|
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
|
|
}
|
|
|
|
const list = await new Promise<{ pathname: string }[]>((resolve, reject) => {
|
|
const stream = minioClient.listObjectsV2(DEFAULT_BUCKET, `${src}/`, true);
|
|
const item: { pathname: string }[] = [];
|
|
|
|
stream.on("data", (v) => {
|
|
if (v && v.name) item.push({ pathname: v.name });
|
|
});
|
|
stream.on("end", () => resolve(item));
|
|
stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE)));
|
|
});
|
|
|
|
await Promise.all(
|
|
list.map(async (v) => {
|
|
const from = `/${DEFAULT_BUCKET}/${v.pathname}`;
|
|
const to = stripLeadingSlash(`${dst}/${v.pathname.slice(`${src}/`.length)}`);
|
|
|
|
const result = await minioClient
|
|
.copyObject(DEFAULT_BUCKET, to, from, copyCond)
|
|
.catch((e) => {
|
|
console.error(`MinIO Error: ${e}`);
|
|
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
|
|
});
|
|
|
|
if (result) {
|
|
if (v.pathname.includes(".keep")) {
|
|
return await minioClient
|
|
.removeObject(DEFAULT_BUCKET, v.pathname)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
}
|
|
|
|
const search = await esClient.search<
|
|
StorageFile & { attachment: Record<string, string> }
|
|
>({
|
|
index: DEFAULT_INDEX!,
|
|
query: { match: { pathname: v.pathname } },
|
|
});
|
|
|
|
if (search && search.hits.hits.length === 0) {
|
|
await minioClient
|
|
.removeObject(DEFAULT_BUCKET, v.pathname)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
return console.log(
|
|
`Trying to update file in storage but not exist in database: ${from} > ${to}`,
|
|
);
|
|
}
|
|
|
|
const data = search.hits.hits[0];
|
|
|
|
await esClient
|
|
.update({
|
|
index: DEFAULT_INDEX!,
|
|
id: data._id,
|
|
doc: {
|
|
pathname: to,
|
|
path: stripLeadingSlash(`${to.split("/").slice(0, -1).join("/")}/`),
|
|
},
|
|
refresh: "wait_for",
|
|
})
|
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
|
|
|
await minioClient
|
|
.removeObject(DEFAULT_BUCKET, v.pathname)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
}
|
|
}),
|
|
);
|
|
|
|
io.getInstance()?.emit("FolderMove", {
|
|
from: stripLeadingSlash(`${src}/`),
|
|
to: stripLeadingSlash(`${dst}/`),
|
|
});
|
|
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* @summary ร้องขอขนาดของแฟ้ม มีหน่อยเป็นไบต์
|
|
*/
|
|
@Post("folder/size")
|
|
@Tags("Storage Folder")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
|
@Example({
|
|
size: 10240,
|
|
})
|
|
public async folderSize(@Body() body: FolderBody) {
|
|
return { size: await folderSize([...body.path, body.name]) };
|
|
}
|
|
|
|
/**
|
|
* ลบแฟ้มออกจากระบบ
|
|
* @summary ลบแฟ้มออกจากระบบ
|
|
*/
|
|
@Delete("folder")
|
|
@Tags("Storage Folder")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
|
public async deleteStorage(@Body() body: DeleteFolderBody) {
|
|
if (body.path.length === 0) {
|
|
throw new HttpError(HttpStatusCode.BAD_REQUEST, "ไม่สามารถลบได้");
|
|
}
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const objects: string[] = [];
|
|
const stream = minioClient.listObjectsV2(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${body.path.join("/")}/`),
|
|
true,
|
|
);
|
|
|
|
stream.on("data", (v) => v && v.name && objects.push(v.name));
|
|
stream.on("close", async () => {
|
|
resolve(await minioClient.removeObjects(DEFAULT_BUCKET, objects));
|
|
});
|
|
stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE)));
|
|
});
|
|
|
|
io.getInstance()?.emit("FolderDelete", { pathname: body.path.join("/") + "/" });
|
|
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* ร้องขอการอัปโหลดไฟล์ โดยเมื่อร้องขอจะได้ URL สำหรับอัพโหลดไฟล์มาพร้อมข้อมูลของไฟล์ที่จะทำการอัพโหลด
|
|
*
|
|
* @summary ร้องขอการอัปโหลดไฟล์
|
|
*/
|
|
@Post("file")
|
|
@Tags("Storage File")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
|
@Example({
|
|
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1.pdf",
|
|
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
|
|
title: "เอกสาร",
|
|
description: "เอกสารการเงิน",
|
|
category: ["บัญชี"],
|
|
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
|
|
upload: false,
|
|
hidden: false,
|
|
fileName: "เอกสาร 1.pdf",
|
|
fileSize: 10240,
|
|
fileType: "application/pdf",
|
|
createdAt: "2021-07-20T12:33:13.018Z",
|
|
createdBy: "admin",
|
|
updatedAt: "2021-07-20T12:33:13.018Z",
|
|
updatedBy: "admin",
|
|
uploadUrl: "s3.storage.upload",
|
|
})
|
|
public async postFile(
|
|
@Request() request: { user: { preferred_username: string } },
|
|
@Body() body: FileBody,
|
|
) {
|
|
const { path, file } = body;
|
|
const validFileName = file.replace(/[/\\?%*:|"<>#]/g, "-").trim();
|
|
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
|
}
|
|
|
|
const result = await esClient
|
|
.search<StorageFile & { attachment?: Record<string, unknown> }>({
|
|
index: DEFAULT_INDEX!,
|
|
query: {
|
|
match: {
|
|
pathname: stripLeadingSlash(`${path.join("/")}/${validFileName}`),
|
|
},
|
|
},
|
|
})
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
|
|
const metadata: StorageFile = {
|
|
path: stripLeadingSlash(`${path.join("/")}/`),
|
|
pathname: stripLeadingSlash(`${path.join("/")}/${validFileName}`),
|
|
fileName: validFileName,
|
|
fileSize: 0, // Will be get by minio object storage after file is uploaded
|
|
fileType: "", // Will be determined by minio object storage after file is uploaded
|
|
title: body.title ?? validFileName, // default to same as filename
|
|
description: body.description ?? "",
|
|
category: body.category ?? [],
|
|
keyword: body.keyword ?? [],
|
|
upload: false, // flag
|
|
hidden: body.hidden ?? false,
|
|
createdAt: new Date().toISOString(),
|
|
createdBy: request.user.preferred_username,
|
|
updatedAt: new Date().toISOString(),
|
|
updatedBy: request.user.preferred_username,
|
|
};
|
|
|
|
// Pathname is unique and should not have multiple record with same path
|
|
if (result && result.hits.hits.length > 0 && result.hits.hits[0]._source) {
|
|
await esClient
|
|
.delete({
|
|
index: DEFAULT_INDEX!,
|
|
id: result.hits.hits[0]._id,
|
|
})
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
|
|
const rec = result.hits.hits[0]._source;
|
|
|
|
// File exist get created at and created by field but all other will be replaced
|
|
// by provided infomation or blank if not provided
|
|
metadata.createdAt = rec.createdAt;
|
|
metadata.createdBy = rec.createdBy;
|
|
}
|
|
|
|
await esClient.index({
|
|
index: DEFAULT_INDEX,
|
|
document: metadata,
|
|
refresh: "wait_for", // Must have or else it doesn't wait for updated index resulted in data not found on fetch
|
|
});
|
|
|
|
!metadata.hidden && io.getInstance()?.emit("FileUploadRequest", metadata);
|
|
|
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, metadata.pathname);
|
|
|
|
return { ...metadata, uploadUrl: presignedUrl };
|
|
}
|
|
|
|
/**
|
|
* @summary ย้ายไฟล์หรือเปลี่ยนชื่อไฟล์
|
|
*/
|
|
@Put("file")
|
|
@Tags("Storage File")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
|
@Response(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
|
@Example({ uploadUrl: "s3.storage.upload" })
|
|
public async moveFile(
|
|
@Request() request: { user: { preferred_username: string } },
|
|
@Body() body: PutFileBody,
|
|
) {
|
|
const search = await esClient
|
|
.search<StorageFile & { attachment: Record<string, any> }>({
|
|
index: DEFAULT_INDEX,
|
|
query: {
|
|
match: {
|
|
pathname: stripLeadingSlash(`${body.from.path.join("/")}/${body.from.file}`),
|
|
},
|
|
},
|
|
})
|
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
|
|
|
if (!search) {
|
|
throw new Error("เกิดข้อผิดพลาดกับระบบฐานข้อมูล กรุณาลองใหม่ในภายหลัง");
|
|
}
|
|
if (search && search.hits.hits.length === 0) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์ดังกล่าว");
|
|
}
|
|
if (
|
|
!(await checkFileExist(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${body.from.path.join("/")}/${body.from.file}`),
|
|
))
|
|
) {
|
|
await esClient.delete({
|
|
index: DEFAULT_INDEX,
|
|
id: search.hits.hits[0]._id,
|
|
});
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์ดังกล่าว");
|
|
}
|
|
if (body.to && JSON.stringify(body.from) !== JSON.stringify(body.to)) {
|
|
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
|
|
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
|
|
}
|
|
if (
|
|
await checkFileExist(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${body.to.path.join("/")}/${body.to.file}`),
|
|
)
|
|
) {
|
|
throw new HttpError(
|
|
HttpStatusCode.PRECONDITION_FAILED,
|
|
"พบไฟล์ในต้ำแหน่งปลายทาง ไม่สามารถย้ายได้",
|
|
);
|
|
}
|
|
}
|
|
if (!search.hits.hits[0]._source) {
|
|
// This should not possible.
|
|
// Just in case the result found with no associated data.
|
|
await esClient.delete({
|
|
index: DEFAULT_INDEX,
|
|
id: search.hits.hits[0]._id,
|
|
});
|
|
throw new Error("ไม่พบข้อมูลในฐานข้อมูล");
|
|
}
|
|
|
|
const id = search.hits.hits[0]._id;
|
|
const { attachment: _, ...source } = search.hits.hits[0]._source;
|
|
|
|
const { to, from, upload, ...metadata } = body;
|
|
|
|
const dateMeta = {
|
|
updatedAt: new Date().toISOString(),
|
|
updatedBy: request.user.preferred_username,
|
|
};
|
|
|
|
if (from && to && JSON.stringify(from) !== JSON.stringify(to)) {
|
|
const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file;
|
|
const dst = stripLeadingSlash(`${to.path.join("/")}/${to.file}`);
|
|
|
|
const result = await minioClient.copyObject(DEFAULT_BUCKET, dst, src, copyCond).catch((e) => {
|
|
console.error(`MinIO Error: ${e}`);
|
|
throw new Error("เกิดข้อผิดพลาด ไม่สามารถย้ายไฟล์ได้");
|
|
});
|
|
|
|
if (result) {
|
|
await esClient
|
|
.update({
|
|
index: DEFAULT_INDEX,
|
|
id: id,
|
|
doc: {
|
|
...metadata,
|
|
path: stripLeadingSlash(`${to.path.join("/")}/`),
|
|
pathname: dst,
|
|
fileName: to.file,
|
|
...dateMeta,
|
|
},
|
|
refresh: "wait_for",
|
|
})
|
|
.then(
|
|
async () =>
|
|
await minioClient.removeObject(
|
|
DEFAULT_BUCKET,
|
|
stripLeadingSlash(`${from.path.join("/")}/${from.file}`),
|
|
),
|
|
)
|
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
|
|
|
if (!source.hidden && !metadata.hidden) {
|
|
io.getInstance()?.emit("FileMove", {
|
|
from: source,
|
|
to: {
|
|
...source,
|
|
...metadata,
|
|
path: stripLeadingSlash(`${to.path.join("/")}/`),
|
|
pathname: dst,
|
|
fileName: to.file,
|
|
...dateMeta,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (upload) {
|
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, dst);
|
|
return { uploadUrl: presignedUrl };
|
|
}
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
}
|
|
|
|
if (from) {
|
|
await esClient
|
|
.update({
|
|
index: DEFAULT_INDEX,
|
|
id: id,
|
|
doc: {
|
|
...metadata,
|
|
...dateMeta,
|
|
},
|
|
refresh: "wait_for",
|
|
})
|
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
|
|
|
if (!source.hidden && !metadata.hidden) {
|
|
io.getInstance()?.emit("FileMove", {
|
|
from: source,
|
|
to: {
|
|
...source,
|
|
...metadata,
|
|
...dateMeta,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (upload) {
|
|
const src = stripLeadingSlash(`${from.path.join("/")}/${from.file}`);
|
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src);
|
|
return { uploadUrl: presignedUrl };
|
|
}
|
|
}
|
|
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* @summary ลบไฟล์ออกจากระบบ
|
|
*/
|
|
@Delete("file")
|
|
@Tags("Storage File")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
|
public async deleteFile(@Body() body: DeleteFileBody) {
|
|
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
|
|
|
|
await minioClient
|
|
.removeObject(DEFAULT_BUCKET, pathname)
|
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
|
await esClient
|
|
.deleteByQuery({
|
|
index: DEFAULT_INDEX,
|
|
query: { match: { pathname } },
|
|
})
|
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
|
|
|
io.getInstance()?.emit("FileDelete", { pathname });
|
|
|
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
|
}
|
|
|
|
/**
|
|
* @summary ขอข้อมูลการโหลดไฟล์
|
|
*/
|
|
@Post("file/download")
|
|
@Tags("Download")
|
|
@Security("bearerAuth", ["management-role", "admin"])
|
|
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
|
|
@Example({
|
|
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1.pdf",
|
|
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
|
|
title: "เอกสาร",
|
|
description: "เอกสารการเงิน",
|
|
category: ["บัญชี"],
|
|
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
|
|
upload: false,
|
|
hidden: false,
|
|
fileName: "เอกสาร 1.pdf",
|
|
fileSize: 10240,
|
|
fileType: "application/pdf",
|
|
createdAt: "2021-07-20T12:33:13.018Z",
|
|
createdBy: "admin",
|
|
updatedAt: "2021-07-20T12:33:13.018Z",
|
|
updatedBy: "admin",
|
|
downloadUrl: "s3.storage.download",
|
|
})
|
|
public async downloadFile(@Body() body: DownloadFileBody) {
|
|
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
|
|
|
|
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
|
|
index: DEFAULT_INDEX,
|
|
query: {
|
|
match: { pathname },
|
|
},
|
|
});
|
|
|
|
if (search && search.hits.hits.length === 0) {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์");
|
|
}
|
|
|
|
const { attachment, ...rest } = search.hits.hits[0]._source!;
|
|
|
|
return {
|
|
...rest,
|
|
downloadUrl: await minioClient.presignedGetObject(DEFAULT_BUCKET, pathname),
|
|
};
|
|
}
|
|
}
|