From 185b1f540a2ae0e93004c584a0783063d9fe5dc8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 12 Dec 2023 13:02:22 +0700 Subject: [PATCH] feat: socketio and user identity --- .../src/controllers/storageController.ts | 102 +++++++++++++++--- Services/server/src/lib/websocket.ts | 2 + 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/Services/server/src/controllers/storageController.ts b/Services/server/src/controllers/storageController.ts index cb6866e..f1e8ecb 100644 --- a/Services/server/src/controllers/storageController.ts +++ b/Services/server/src/controllers/storageController.ts @@ -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 esClient from "../elasticsearch"; @@ -9,6 +21,8 @@ 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."); @@ -200,6 +214,7 @@ export class StorageController extends Controller { }, ]) @Tags("Storage Folder", "Storage File") + @Security("bearerAuth") public async getList(@Body() body: ListRequestBody) { const path = body.path.filter(Boolean); @@ -209,29 +224,41 @@ export class StorageController extends Controller { @Post("folder") @Tags("Storage Folder") + @Security("bearerAuth", ["management-role", "admin"]) @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; if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) { 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, `${path.join("/")}/${name.replace(/[/\\?%*:|"<>#]/g, "-").trim()}/.keep`, "", 0, - { - createdAt: new Date().toISOString(), - createdBy: "n/a", - }, + meta, ) .catch((e) => console.error(`MinIO Error: ${e}`)); 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); } @@ -240,6 +267,7 @@ export class StorageController extends Controller { */ @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}`; @@ -322,6 +350,11 @@ export class StorageController extends Controller { }), ); + io.getInstance()?.emit("FolderMove", { + from: `${src}/`, + to: `${dst}/`, + }); + return this.setStatus(HttpStatusCode.NO_CONTENT); } @@ -330,6 +363,7 @@ export class StorageController extends Controller { */ @Delete("folder") @Tags("Storage Folder") + @Security("bearerAuth", ["management-role", "admin"]) @SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ") public async deleteStorage(@Body() body: DeleteFolderBody) { await new Promise((resolve, reject) => { @@ -343,6 +377,8 @@ export class StorageController extends Controller { stream.on("error", () => reject(new Error(MINIO_ERROR_MESSAGE))); }); + io.getInstance()?.emit("FolderDelete", { pathname: body.path.join("/") + "/" }); + return this.setStatus(HttpStatusCode.NO_CONTENT); } @@ -351,8 +387,12 @@ export class StorageController extends Controller { */ @Post("file") @Tags("Storage File") + @Security("bearerAuth", ["management-role", "admin"]) @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; if (!(await checkPathExist(DEFAULT_BUCKET, path.join("/")))) { @@ -378,9 +418,9 @@ export class StorageController extends Controller { keyword: body.keyword ?? [], upload: false, // flag createdAt: new Date().toISOString(), - createdBy: "n/a", + createdBy: request.user.preferred_username, updatedAt: new Date().toISOString(), - updatedBy: "n/a", + updatedBy: request.user.preferred_username, }; // 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 }); + io.getInstance()?.emit("FileUploadRequest", metadata); + const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, metadata.pathname); return { ...metadata, uploadUrl: presignedUrl }; @@ -413,8 +455,12 @@ export class StorageController extends Controller { @Put("file") @Tags("Storage File") + @Security("bearerAuth", ["management-role", "admin"]) @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 .search }>({ index: DEFAULT_INDEX, @@ -460,9 +506,15 @@ export class StorageController extends Controller { } 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) { const src = [DEFAULT_BUCKET, ...from.path, ""].join("/") + from.file; const dst = to.path.join("/") + `/${to.file}`; @@ -481,14 +533,24 @@ export class StorageController extends Controller { ...metadata, path: to.path.join("/") + "/", pathname: dst, - updatedAt: new Date().toISOString(), - updatedBy: "n/a", + ...dateMeta, }, refresh: "wait_for", }) .then(async () => await minioClient.removeObject(DEFAULT_INDEX, src)) .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) { const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, dst); return { uploadUrl: presignedUrl }; @@ -503,12 +565,21 @@ export class StorageController extends Controller { id: id, doc: { ...metadata, - updatedAt: new Date().toISOString(), - updatedBy: "n/a", + ...dateMeta, }, refresh: "wait_for", }) .catch((e) => console.error(`ElasticSearch Error: ${e}`)); + + io.getInstance()?.emit("FileMove", { + from: source, + to: { + ...source, + ...metadata, + ...dateMeta, + }, + }); + if (upload) { const src = from.path.join("/") + `/${from.file}`; const presignedUrl = await minioClient.presignedPutObject(DEFAULT_BUCKET, src); @@ -521,6 +592,7 @@ export class StorageController extends Controller { @Delete("file") @Tags("Storage File") + @Security("bearerAuth", ["management-role", "admin"]) @SuccessResponse(HttpStatusCode.NO_CONTENT, "สำเร็จ") public async deleteFile(@Body() body: DeleteFileBody) { const pathname = body.path.join("/") + body.file; @@ -535,6 +607,8 @@ export class StorageController extends Controller { }) .catch((e) => console.error(`ElasticSearch Error: ${e}`)); + io.getInstance()?.emit("FileDelete", { pathname }); + return this.setStatus(HttpStatusCode.NO_CONTENT); } } diff --git a/Services/server/src/lib/websocket.ts b/Services/server/src/lib/websocket.ts index 6783db2..e8f4dea 100644 --- a/Services/server/src/lib/websocket.ts +++ b/Services/server/src/lib/websocket.ts @@ -9,3 +9,5 @@ export function setInstance(server: Server) { export function getInstance() { return io; } + +export default { getInstance, setInstance };