fix: path leading slash and remove unused endpoint
This commit is contained in:
parent
d1abd6684e
commit
51528afc74
12 changed files with 295 additions and 5364 deletions
|
|
@ -6,6 +6,7 @@ import {
|
|||
Post,
|
||||
Put,
|
||||
Request,
|
||||
Response,
|
||||
Route,
|
||||
Security,
|
||||
SuccessResponse,
|
||||
|
|
@ -114,6 +115,10 @@ interface DownloadFileBody {
|
|||
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(
|
||||
|
|
@ -138,7 +143,7 @@ async function listFolder(path: string[], hidden: boolean = false) {
|
|||
|
||||
const stream = minioClient.listObjectsV2(
|
||||
DEFAULT_BUCKET,
|
||||
path.length === 0 ? "" : path.join("/") + "/",
|
||||
stripLeadingSlash(`${path.join("/")}/`),
|
||||
);
|
||||
stream.on("data", (v) => {
|
||||
if (v && v.prefix)
|
||||
|
|
@ -182,7 +187,7 @@ async function listFile(path: string[], hidden: boolean = false) {
|
|||
sort: [{ pathname: "asc" }],
|
||||
query: {
|
||||
bool: {
|
||||
must: { match: { path: path.join("/") + "/" } },
|
||||
must: { match: { path: stripLeadingSlash(`${path.join("/")}/`) } },
|
||||
must_not: !hidden ? { match: { hidden: true } } : undefined,
|
||||
},
|
||||
},
|
||||
|
|
@ -202,14 +207,14 @@ async function listFile(path: string[], hidden: boolean = false) {
|
|||
return records;
|
||||
}
|
||||
|
||||
async function checkPathExist(bucket: string, path: string) {
|
||||
if (path.split("/").filter(Boolean).length === 0) return true; // root does not contain any mark
|
||||
return await checkFileExist(bucket, `${path}/.keep`);
|
||||
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, pathname).catch((e) => {
|
||||
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);
|
||||
|
|
@ -219,16 +224,21 @@ async function checkFileExist(bucket: string, pathname: string) {
|
|||
|
||||
@Route("storage")
|
||||
export class StorageController extends Controller {
|
||||
/**
|
||||
* @summary แสดงรายการแฟ้มหรือไฟล์
|
||||
*/
|
||||
@Post("list")
|
||||
@Tags("Storage Folder", "Storage File")
|
||||
@Security("bearerAuth")
|
||||
@Example([
|
||||
{
|
||||
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1",
|
||||
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
|
||||
name: "แฟ้ม 1",
|
||||
createdAt: "2021-07-20T12:33:13.018Z",
|
||||
createdBy: "admin",
|
||||
},
|
||||
{
|
||||
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2",
|
||||
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 2/",
|
||||
name: "แฟ้ม 2",
|
||||
createdAt: "2022-01-23T16:05:02.114Z",
|
||||
createdBy: "admin",
|
||||
|
|
@ -243,6 +253,7 @@ export class StorageController extends Controller {
|
|||
category: ["บัญชี"],
|
||||
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
|
||||
upload: false,
|
||||
hidden: false,
|
||||
fileName: "เอกสาร 1.pdf",
|
||||
fileSize: 10240,
|
||||
fileType: "application/pdf",
|
||||
|
|
@ -252,15 +263,21 @@ export class StorageController extends Controller {
|
|||
updatedBy: "admin",
|
||||
},
|
||||
])
|
||||
@Tags("Storage Folder", "Storage File")
|
||||
@Security("bearerAuth")
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary สร้างแฟ้ม
|
||||
*/
|
||||
@Post("folder")
|
||||
@Tags("Storage Folder")
|
||||
@Security("bearerAuth", ["management-role", "admin"])
|
||||
|
|
@ -271,7 +288,7 @@ export class StorageController extends Controller {
|
|||
) {
|
||||
const { path, name } = body;
|
||||
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, path))) {
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +300,7 @@ export class StorageController extends Controller {
|
|||
const created = await minioClient
|
||||
.putObject(
|
||||
DEFAULT_BUCKET,
|
||||
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`,
|
||||
stripLeadingSlash(`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`),
|
||||
"",
|
||||
0,
|
||||
meta,
|
||||
|
|
@ -293,7 +310,9 @@ export class StorageController extends Controller {
|
|||
if (!created) throw new Error(MINIO_ERROR_MESSAGE);
|
||||
|
||||
io.getInstance()?.emit("FolderCreate", {
|
||||
pathname: `${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
|
||||
pathname: stripLeadingSlash(
|
||||
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
|
||||
),
|
||||
name: name.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
|
||||
...meta,
|
||||
});
|
||||
|
|
@ -302,23 +321,26 @@ export class StorageController extends Controller {
|
|||
}
|
||||
|
||||
/**
|
||||
* ยา้ย Folder ภายใต้ Folder (Path) หนึ่ง ไปภายใน Folder (Path) หนึ่งและสามารถเปลี่ยนชื่อได้
|
||||
* ยา้ยแฟ้มภายในตำแหน่งหนึ่งไปภายในอีกแฟ้มหนึ่ง
|
||||
* หรือเปลี่ยนชื่อได้
|
||||
*
|
||||
* @summary ยา้ยแฟ้ม
|
||||
*/
|
||||
@Put("folder")
|
||||
@Tags("Storage Folder")
|
||||
@Security("bearerAuth", ["management-role", "admin"])
|
||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||
public async moveFolder(@Body() body: PutFolderBody) {
|
||||
const src = `${body.from.path.join("/")}/${body.from.name}`;
|
||||
const dst = `${body.to.path.join("/")}/${body.to.name}`;
|
||||
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))) {
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, src.split("/")))) {
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
||||
}
|
||||
if (await checkPathExist(DEFAULT_BUCKET, dst)) {
|
||||
if (await checkPathExist(DEFAULT_BUCKET, dst.split("/"))) {
|
||||
throw new HttpError(HttpStatusCode.CONFLICT, PATH_ALREADY_EXIST);
|
||||
}
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path.join("/")))) {
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
|
||||
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +358,7 @@ export class StorageController extends Controller {
|
|||
await Promise.all(
|
||||
list.map(async (v) => {
|
||||
const from = `/${DEFAULT_BUCKET}/${v.pathname}`;
|
||||
const to = `${dst}/${v.pathname.slice(`${src}/`.length)}`;
|
||||
const to = stripLeadingSlash(`${dst}/${v.pathname.slice(`${src}/`.length)}`);
|
||||
|
||||
const result = await minioClient
|
||||
.copyObject(DEFAULT_BUCKET, to, from, copyCond)
|
||||
|
|
@ -376,7 +398,7 @@ export class StorageController extends Controller {
|
|||
id: data._id,
|
||||
doc: {
|
||||
pathname: to,
|
||||
path: to.split("/").slice(0, -1).join("/") + "/",
|
||||
path: stripLeadingSlash(`${to.split("/").slice(0, -1).join("/")}/`),
|
||||
},
|
||||
refresh: "wait_for",
|
||||
})
|
||||
|
|
@ -390,8 +412,8 @@ export class StorageController extends Controller {
|
|||
);
|
||||
|
||||
io.getInstance()?.emit("FolderMove", {
|
||||
from: `${src}/`,
|
||||
to: `${dst}/`,
|
||||
from: stripLeadingSlash(`${src}/`),
|
||||
to: stripLeadingSlash(`${dst}/`),
|
||||
});
|
||||
|
||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||
|
|
@ -400,6 +422,10 @@ export class StorageController extends Controller {
|
|||
@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]) };
|
||||
}
|
||||
|
|
@ -412,9 +438,17 @@ export class StorageController extends Controller {
|
|||
@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, body.path.join("/") + "/", true);
|
||||
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 () => {
|
||||
|
|
@ -435,30 +469,53 @@ export class StorageController extends Controller {
|
|||
@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.join("/")))) {
|
||||
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: path.join("/") + "/" } },
|
||||
query: {
|
||||
match: {
|
||||
pathname: stripLeadingSlash(`${path.join("/")}/${validFileName}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((e) => console.error(`MinIO Error: ${e}`));
|
||||
|
||||
const metadata: StorageFile = {
|
||||
path: path.join("/") + "/",
|
||||
pathname: path.join("/") + "/" + file.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
|
||||
fileName: file.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
|
||||
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 ?? file.replace(/[/\\?%*:|"<>#]/g, "-").trim(), // default to same as filename
|
||||
title: body.title ?? validFileName, // default to same as filename
|
||||
description: body.description ?? "",
|
||||
category: body.category ?? [],
|
||||
keyword: body.keyword ?? [],
|
||||
|
|
@ -504,6 +561,8 @@ export class StorageController extends Controller {
|
|||
@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,
|
||||
|
|
@ -512,7 +571,9 @@ export class StorageController extends Controller {
|
|||
.search<StorageFile & { attachment: Record<string, any> }>({
|
||||
index: DEFAULT_INDEX,
|
||||
query: {
|
||||
match: { pathname: body.from.path.join("/") + `/${body.from.file}` },
|
||||
match: {
|
||||
pathname: stripLeadingSlash(`${body.from.path.join("/")}/${body.from.file}`),
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
||||
|
|
@ -523,7 +584,12 @@ export class StorageController extends Controller {
|
|||
if (search && search.hits.hits.length === 0) {
|
||||
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบไฟล์ดังกล่าว");
|
||||
}
|
||||
if (!(await checkFileExist(DEFAULT_BUCKET, body.from.path.join("/") + `/${body.from.file}`))) {
|
||||
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,
|
||||
|
|
@ -531,10 +597,15 @@ export class StorageController extends Controller {
|
|||
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.join("/")))) {
|
||||
if (!(await checkPathExist(DEFAULT_BUCKET, body.to.path))) {
|
||||
throw new HttpError(HttpStatusCode.PRECONDITION_FAILED, "ไม่พบตำแหน่งที่ต้องการย้าย");
|
||||
}
|
||||
if (await checkFileExist(DEFAULT_BUCKET, body.to.path.join("/") + `/${body.to.file}`)) {
|
||||
if (
|
||||
await checkFileExist(
|
||||
DEFAULT_BUCKET,
|
||||
stripLeadingSlash(`${body.to.path.join("/")}/${body.to.file}`),
|
||||
)
|
||||
) {
|
||||
throw new HttpError(
|
||||
HttpStatusCode.PRECONDITION_FAILED,
|
||||
"พบไฟล์ในต้ำแหน่งปลายทาง ไม่สามารถย้ายได้",
|
||||
|
|
@ -563,7 +634,7 @@ export class StorageController extends Controller {
|
|||
|
||||
if (from && to && JSON.stringify(from) !== JSON.stringify(to)) {
|
||||
const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file;
|
||||
const dst = to.path.join("/") + `/${to.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}`);
|
||||
|
|
@ -577,7 +648,7 @@ export class StorageController extends Controller {
|
|||
id: id,
|
||||
doc: {
|
||||
...metadata,
|
||||
path: to.path.join("/") + "/",
|
||||
path: stripLeadingSlash(`${to.path.join("/")}/`),
|
||||
pathname: dst,
|
||||
fileName: to.file,
|
||||
...dateMeta,
|
||||
|
|
@ -586,7 +657,10 @@ export class StorageController extends Controller {
|
|||
})
|
||||
.then(
|
||||
async () =>
|
||||
await minioClient.removeObject(DEFAULT_BUCKET, from.path.join("/") + `/${from.file}`),
|
||||
await minioClient.removeObject(
|
||||
DEFAULT_BUCKET,
|
||||
stripLeadingSlash(`${from.path.join("/")}/${from.file}`),
|
||||
),
|
||||
)
|
||||
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
||||
|
||||
|
|
@ -595,7 +669,7 @@ export class StorageController extends Controller {
|
|||
to: {
|
||||
...source,
|
||||
...metadata,
|
||||
path: to.path.join("/") + "/",
|
||||
path: stripLeadingSlash(`${to.path.join("/")}/`),
|
||||
pathname: dst,
|
||||
fileName: to.file,
|
||||
...dateMeta,
|
||||
|
|
@ -633,7 +707,7 @@ export class StorageController extends Controller {
|
|||
});
|
||||
|
||||
if (upload) {
|
||||
const src = from.path.join("/") + `/${from.file}`;
|
||||
const src = stripLeadingSlash(`${from.path.join("/")}/${from.file}`);
|
||||
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src);
|
||||
return { uploadUrl: presignedUrl };
|
||||
}
|
||||
|
|
@ -650,7 +724,7 @@ export class StorageController extends Controller {
|
|||
@Security("bearerAuth", ["management-role", "admin"])
|
||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||
public async deleteFile(@Body() body: DeleteFileBody) {
|
||||
const pathname = body.path.join("/") + `/${body.file}`;
|
||||
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
|
||||
|
||||
await minioClient
|
||||
.removeObject(DEFAULT_BUCKET, pathname)
|
||||
|
|
@ -671,8 +745,26 @@ export class StorageController extends Controller {
|
|||
@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 = body.path.join("/") + `/${body.file}`;
|
||||
const pathname = stripLeadingSlash(`${body.path.join("/")}/${body.file}`);
|
||||
|
||||
const search = await esClient.search<StorageFile & { attachment: Record<string, string> }>({
|
||||
index: DEFAULT_INDEX,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue