feat: socketio and user identity
This commit is contained in:
parent
cd43bb53c3
commit
185b1f540a
2 changed files with 90 additions and 14 deletions
|
|
@ -1,4 +1,16 @@
|
||||||
import { Body, Controller, Delete, Example, Post, Put, Route, SuccessResponse, Tags } from "tsoa";
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Example,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Request,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
SuccessResponse,
|
||||||
|
Tags,
|
||||||
|
} from "tsoa";
|
||||||
|
|
||||||
import minioClient from "../minio";
|
import minioClient from "../minio";
|
||||||
import esClient from "../elasticsearch";
|
import esClient from "../elasticsearch";
|
||||||
|
|
@ -9,6 +21,8 @@ import { StorageFile, StorageFolder } from "../interfaces/storage-fs";
|
||||||
|
|
||||||
import { copyCond } from "../utils/minio";
|
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.MINIO_BUCKET) throw Error("Default MinIO bucket must be specified.");
|
||||||
if (!process.env.ELASTICSEARCH_INDEX) throw Error("Default ElasticSearch index must be specified.");
|
if (!process.env.ELASTICSEARCH_INDEX) throw Error("Default ElasticSearch index must be specified.");
|
||||||
|
|
||||||
|
|
@ -200,6 +214,7 @@ export class StorageController extends Controller {
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@Tags("Storage Folder", "Storage File")
|
@Tags("Storage Folder", "Storage File")
|
||||||
|
@Security("bearerAuth")
|
||||||
public async getList(@Body() body: ListRequestBody) {
|
public async getList(@Body() body: ListRequestBody) {
|
||||||
const path = body.path.filter(Boolean);
|
const path = body.path.filter(Boolean);
|
||||||
|
|
||||||
|
|
@ -209,29 +224,41 @@ export class StorageController extends Controller {
|
||||||
|
|
||||||
@Post("folder")
|
@Post("folder")
|
||||||
@Tags("Storage Folder")
|
@Tags("Storage Folder")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async postFolder(@Body() body: FolderBody) {
|
public async postFolder(
|
||||||
|
@Request() request: { user: { preferred_username: string } },
|
||||||
|
@Body() body: FolderBody,
|
||||||
|
) {
|
||||||
const { path, name } = body;
|
const { path, name } = body;
|
||||||
|
|
||||||
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
|
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
|
||||||
throw new HttpError(HttpStatusCode.NOT_FOUND, PATH_NOT_FOUND_MESSAGE);
|
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
|
const created = await minioClient
|
||||||
.putObject(
|
.putObject(
|
||||||
DEFAULT_BUCKET,
|
DEFAULT_BUCKET,
|
||||||
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`,
|
`${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`,
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
{
|
meta,
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
createdBy: "n/a",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.catch((e) => console.error(`MinIO Error: ${e}`));
|
.catch((e) => console.error(`MinIO Error: ${e}`));
|
||||||
|
|
||||||
if (!created) throw new Error(MINIO_ERROR_MESSAGE);
|
if (!created) throw new Error(MINIO_ERROR_MESSAGE);
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FolderCreate", {
|
||||||
|
pathname: `${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/`,
|
||||||
|
name: name.replace(/[/\\?%*:|"<>#]/g, "-").trim(),
|
||||||
|
...meta,
|
||||||
|
});
|
||||||
|
|
||||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,6 +267,7 @@ export class StorageController extends Controller {
|
||||||
*/
|
*/
|
||||||
@Put("folder")
|
@Put("folder")
|
||||||
@Tags("Storage Folder")
|
@Tags("Storage Folder")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async moveFolder(@Body() body: PutFolderBody) {
|
public async moveFolder(@Body() body: PutFolderBody) {
|
||||||
const src = `${body.from.path.join("/")}/${body.from.name}`;
|
const src = `${body.from.path.join("/")}/${body.from.name}`;
|
||||||
|
|
@ -322,6 +350,11 @@ export class StorageController extends Controller {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FolderMove", {
|
||||||
|
from: `${src}/`,
|
||||||
|
to: `${dst}/`,
|
||||||
|
});
|
||||||
|
|
||||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,6 +363,7 @@ export class StorageController extends Controller {
|
||||||
*/
|
*/
|
||||||
@Delete("folder")
|
@Delete("folder")
|
||||||
@Tags("Storage Folder")
|
@Tags("Storage Folder")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async deleteStorage(@Body() body: DeleteFolderBody) {
|
public async deleteStorage(@Body() body: DeleteFolderBody) {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
|
@ -343,6 +377,8 @@ export class StorageController extends Controller {
|
||||||
stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE)));
|
stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FolderDelete", { pathname: body.path.join("/") + "/" });
|
||||||
|
|
||||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,8 +387,12 @@ export class StorageController extends Controller {
|
||||||
*/
|
*/
|
||||||
@Post("file")
|
@Post("file")
|
||||||
@Tags("Storage File")
|
@Tags("Storage File")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async postFile(@Body() body: FileBody) {
|
public async postFile(
|
||||||
|
@Request() request: { user: { preferred_username: string } },
|
||||||
|
@Body() body: FileBody,
|
||||||
|
) {
|
||||||
const { path, file } = body;
|
const { path, file } = body;
|
||||||
|
|
||||||
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
|
if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) {
|
||||||
|
|
@ -378,9 +418,9 @@ export class StorageController extends Controller {
|
||||||
keyword: body.keyword ?? [],
|
keyword: body.keyword ?? [],
|
||||||
upload: false, // flag
|
upload: false, // flag
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
createdBy: "n/a",
|
createdBy: request.user.preferred_username,
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
updatedBy: "n/a",
|
updatedBy: request.user.preferred_username,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pathname is unique and should not have multiple record with same path
|
// Pathname is unique and should not have multiple record with same path
|
||||||
|
|
@ -406,6 +446,8 @@ export class StorageController extends Controller {
|
||||||
refresh: "wait_for", // Must have or else it doesn't wait for updated index resulted in data not found on fetch
|
refresh: "wait_for", // Must have or else it doesn't wait for updated index resulted in data not found on fetch
|
||||||
});
|
});
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FileUploadRequest", metadata);
|
||||||
|
|
||||||
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, metadata.pathname);
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, metadata.pathname);
|
||||||
|
|
||||||
return { ...metadata, uploadUrl: presignedUrl };
|
return { ...metadata, uploadUrl: presignedUrl };
|
||||||
|
|
@ -413,8 +455,12 @@ export class StorageController extends Controller {
|
||||||
|
|
||||||
@Put("file")
|
@Put("file")
|
||||||
@Tags("Storage File")
|
@Tags("Storage File")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async moveFile(@Body() body: PutFileBody) {
|
public async moveFile(
|
||||||
|
@Request() request: { user: { preferred_username: string } },
|
||||||
|
@Body() body: PutFileBody,
|
||||||
|
) {
|
||||||
const search = await esClient
|
const search = await esClient
|
||||||
.search<StorageFile & { attachment: Record<string, any> }>({
|
.search<StorageFile & { attachment: Record<string, any> }>({
|
||||||
index: DEFAULT_INDEX,
|
index: DEFAULT_INDEX,
|
||||||
|
|
@ -460,9 +506,15 @@ export class StorageController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = search.hits.hits[0]._id;
|
const id = search.hits.hits[0]._id;
|
||||||
|
const { attachment: _, ...source } = search.hits.hits[0]._source;
|
||||||
|
|
||||||
const { to, from, upload, ...metadata } = body;
|
const { to, from, upload, ...metadata } = body;
|
||||||
|
|
||||||
|
const dateMeta = {
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
updatedBy: request.user.preferred_username,
|
||||||
|
};
|
||||||
|
|
||||||
if (from && to) {
|
if (from && to) {
|
||||||
const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file;
|
const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file;
|
||||||
const dst = to.path.join("/") + `/${to.file}`;
|
const dst = to.path.join("/") + `/${to.file}`;
|
||||||
|
|
@ -481,14 +533,24 @@ export class StorageController extends Controller {
|
||||||
...metadata,
|
...metadata,
|
||||||
path: to.path.join("/") + "/",
|
path: to.path.join("/") + "/",
|
||||||
pathname: dst,
|
pathname: dst,
|
||||||
updatedAt: new Date().toISOString(),
|
...dateMeta,
|
||||||
updatedBy: "n/a",
|
|
||||||
},
|
},
|
||||||
refresh: "wait_for",
|
refresh: "wait_for",
|
||||||
})
|
})
|
||||||
.then(async () => await minioClient.removeObject(DEFAULT_INDEX, src))
|
.then(async () => await minioClient.removeObject(DEFAULT_INDEX, src))
|
||||||
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FileMove", {
|
||||||
|
from: source,
|
||||||
|
to: {
|
||||||
|
...source,
|
||||||
|
...metadata,
|
||||||
|
path: to.path.join("/") + "/",
|
||||||
|
pathname: dst,
|
||||||
|
...dateMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (upload) {
|
if (upload) {
|
||||||
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, dst);
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, dst);
|
||||||
return { uploadUrl: presignedUrl };
|
return { uploadUrl: presignedUrl };
|
||||||
|
|
@ -503,12 +565,21 @@ export class StorageController extends Controller {
|
||||||
id: id,
|
id: id,
|
||||||
doc: {
|
doc: {
|
||||||
...metadata,
|
...metadata,
|
||||||
updatedAt: new Date().toISOString(),
|
...dateMeta,
|
||||||
updatedBy: "n/a",
|
|
||||||
},
|
},
|
||||||
refresh: "wait_for",
|
refresh: "wait_for",
|
||||||
})
|
})
|
||||||
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FileMove", {
|
||||||
|
from: source,
|
||||||
|
to: {
|
||||||
|
...source,
|
||||||
|
...metadata,
|
||||||
|
...dateMeta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (upload) {
|
if (upload) {
|
||||||
const src = from.path.join("/") + `/${from.file}`;
|
const src = from.path.join("/") + `/${from.file}`;
|
||||||
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src);
|
const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src);
|
||||||
|
|
@ -521,6 +592,7 @@ export class StorageController extends Controller {
|
||||||
|
|
||||||
@Delete("file")
|
@Delete("file")
|
||||||
@Tags("Storage File")
|
@Tags("Storage File")
|
||||||
|
@Security("bearerAuth", ["management-role", "admin"])
|
||||||
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
@SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ")
|
||||||
public async deleteFile(@Body() body: DeleteFileBody) {
|
public async deleteFile(@Body() body: DeleteFileBody) {
|
||||||
const pathname = body.path.join("/") + body.file;
|
const pathname = body.path.join("/") + body.file;
|
||||||
|
|
@ -535,6 +607,8 @@ export class StorageController extends Controller {
|
||||||
})
|
})
|
||||||
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
.catch((e) => console.error(`ElasticSearch Error: ${e}`));
|
||||||
|
|
||||||
|
io.getInstance()?.emit("FileDelete", { pathname });
|
||||||
|
|
||||||
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
return this.setStatus(HttpStatusCode.NO_CONTENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,5 @@ export function setInstance(server: Server) {
|
||||||
export function getInstance() {
|
export function getInstance() {
|
||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { getInstance, setInstance };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue