From 4db3ab4c843ab5aaf668c8a5958fd642f606ec52 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:41:44 +0700 Subject: [PATCH 1/9] feat: add socket server for notification --- src/app.ts | 3 ++ src/services/socket.ts | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/services/socket.ts diff --git a/src/app.ts b/src/app.ts index 5b35577..b18eaba 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,10 +6,13 @@ import swaggerUi from "swagger-ui-express"; import swaggerDocument from "./swagger.json"; import error from "./middlewares/error"; import { RegisterRoutes } from "./routes"; +import { initWebSocket } from "./services/socket"; async function main() { const app = express(); + initWebSocket(+(process.env.SOCKET_PORT || 3001)); + app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); diff --git a/src/services/socket.ts b/src/services/socket.ts new file mode 100644 index 0000000..3e4aca1 --- /dev/null +++ b/src/services/socket.ts @@ -0,0 +1,69 @@ +import { Server } from "socket.io"; + +let io: Server; + +export function initWebSocket(port?: number) { + if (io) return; + + io = new Server({ cors: { origin: "*" }, path: "/api/v1/backup-socket" }); + + io.use(async (socket, next) => { + const token = socket.handshake.auth.token; + + const res = await fetch(`${process.env.AUTH_REALM_URL}/protocol/openid-connect/userinfo`, { + headers: { authorization: `Bearer ${token}` }, + }).catch((e) => console.error(e)); + + if (res?.ok) { + socket.data.user = await res.json(); + } + + next(); + }); + + io.on("connection", (ws) => { + console.log("✅ Client connected to WebSocket"); + + ws.on("close", () => { + console.log("❌ Client disconnected"); + }); + + ws.on("error", (error: any) => { + console.error("WebSocket error:", error); + }); + }); + + io.listen(port || 3001); +} + +export async function sendWebSocket( + event: string, + data: any, + opts?: { + roles?: string[]; + userId?: string[]; + }, +) { + if (!io) initWebSocket(); + + for (let [id, session] of io.of("/").sockets) { + const user: { + sub: string; + name: string; + given_name: string; + family_name: string; + preferred_username: string; + email: string; + role: string[]; + } = session.data.user; + + if (!user) continue; + + if ( + user.role?.some((v) => opts?.roles?.includes(v)) || + opts?.userId?.some((v) => user.sub === v) + ) { + io.to(id).emit(event, JSON.stringify(data)); + } + } +} From 93d3c7503554394308f82819798b4f57a673c61a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:02:44 +0700 Subject: [PATCH 2/9] feat: allow array of string as send socket params --- src/services/socket.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/socket.ts b/src/services/socket.ts index 3e4aca1..190eaac 100644 --- a/src/services/socket.ts +++ b/src/services/socket.ts @@ -40,8 +40,8 @@ export async function sendWebSocket( event: string, data: any, opts?: { - roles?: string[]; - userId?: string[]; + roles?: string | string[]; + userId?: string | string[]; }, ) { if (!io) initWebSocket(); @@ -59,6 +59,9 @@ export async function sendWebSocket( if (!user) continue; + if (typeof opts?.roles === "string") opts.roles = [opts.roles]; + if (typeof opts?.userId === "string") opts.userId = [opts.userId]; + if ( user.role?.some((v) => opts?.roles?.includes(v)) || opts?.userId?.some((v) => user.sub === v) From 59841a609ab758a80afb5b0810de29d94cd44c1b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:04:43 +0700 Subject: [PATCH 3/9] feat: expose backup endpoint notification --- src/controllers/backup-controller.ts | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/controllers/backup-controller.ts b/src/controllers/backup-controller.ts index cd23c43..7ae4ba3 100644 --- a/src/controllers/backup-controller.ts +++ b/src/controllers/backup-controller.ts @@ -16,6 +16,7 @@ import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { randomUUID } from "crypto"; import { getFile } from "../services/minio"; +import { sendWebSocket } from "../services/socket"; function getEnvVar(environmentName: string) { const environmentValue = process.env[environmentName]; @@ -55,9 +56,9 @@ function jsonParseOrPlainText(str: string) { } @Route("/api/v1/backup") -@Security("keycloak") export class BackupController extends Controller { @Get() + @Security("keycloak") async listBackup() { const data = await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/jobs/run_wait_result/p/${WINDMILL_BACKUP_LIST_SCRIPT_PATH}`, @@ -99,6 +100,7 @@ export class BackupController extends Controller { } @Get("backup-running-list") + @Security("keycloak") async runningBackupStatus() { return await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/jobs/list?running=true&script_path_exact=${WINDMILL_BACKUP_FLOW_PATH}`, @@ -116,6 +118,7 @@ export class BackupController extends Controller { } @Get("restore-running-list") + @Security("keycloak") async runningRestoreStatus() { return await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/jobs/list?running=true&script_path_exact=${WINDMILL_RESTORE_FLOW_PATH}`, @@ -126,7 +129,11 @@ export class BackupController extends Controller { } @Post("create") - async runBackup(@Body() body?: { name?: string }) { + @Security("keycloak") + async runBackup( + @Request() req: Request & { user: { sub: string } }, + @Body() body?: { name?: string }, + ) { const timestamp = Math.round(Date.now() / 1000); const name = body?.name && body.name !== "auto-backup" @@ -148,6 +155,7 @@ export class BackupController extends Controller { "Content-Type": "application/json", }, body: JSON.stringify({ + triggerUserId: req.user.sub, backup_name: name, storage: { s3_source_endpoint: s3TargetUrl, @@ -175,6 +183,7 @@ export class BackupController extends Controller { } @Get("download/{name}") + @Security("keycloak") async downloadBackup( @Request() req: express.Request, @Path() name: string, @@ -188,6 +197,7 @@ export class BackupController extends Controller { } @Post("restore") + @Security("keycloak") async restoreBackup(@Body() body: { name: string }) { const listRunning = await this.runningRestoreStatus(); @@ -230,6 +240,7 @@ export class BackupController extends Controller { } @Delete("delete") + @Security("keycloak") async deleteBackup(@Body() body: { name: string }) { await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/jobs/run/p/${WINDMILL_BACKUP_DELETE_SCRIPT_PATH}`, @@ -253,6 +264,7 @@ export class BackupController extends Controller { } @Get("schedule") + @Security("keycloak") async listSchedule() { const result = await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/list?path=${WINDMILL_BACKUP_FLOW_PATH}`, @@ -280,6 +292,7 @@ export class BackupController extends Controller { } @Post("schedule") + @Security("keycloak") async createSchedule( @Body() body: { name: string; schedule: string; timezone?: string; startAt?: Date }, ) { @@ -331,6 +344,7 @@ export class BackupController extends Controller { } @Put("schedule/{id}") + @Security("keycloak") async updateSchedule( @Path() id: string, @Body() @@ -401,6 +415,7 @@ export class BackupController extends Controller { } @Post("schedule/{id}/toggle") + @Security("keycloak") async toggleSchedule(@Path() id: string) { return await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/get/f/backup_schedule/${id}`, @@ -431,6 +446,7 @@ export class BackupController extends Controller { } @Delete("schedule/{id}") + @Security("keycloak") async deleteSchedule(@Path() id: string) { return await fetch( `${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/delete/f/backup_schedule/${id}`, @@ -443,4 +459,14 @@ export class BackupController extends Controller { }, ).then(async (r) => jsonParseOrPlainText(await r.text())); } + + @Post("notify-backup") + async notifyBackup( + @Body() payload: { message: string; userId?: string | string[]; roles?: string | string[] }, + ) { + sendWebSocket("backup-notification", payload.message, { + roles: payload.roles || [], + userId: payload.userId || [], + }); + } } From b7b65a966b00b13e1b4eb1d92594049bf0410f3d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:05:08 +0700 Subject: [PATCH 4/9] feat: add triggered by metadata --- src/controllers/backup-controller.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/controllers/backup-controller.ts b/src/controllers/backup-controller.ts index 7ae4ba3..ffe98ae 100644 --- a/src/controllers/backup-controller.ts +++ b/src/controllers/backup-controller.ts @@ -131,7 +131,7 @@ export class BackupController extends Controller { @Post("create") @Security("keycloak") async runBackup( - @Request() req: Request & { user: { sub: string } }, + @Request() req: Request & { user: { sub: string; preferred_username: string } }, @Body() body?: { name?: string }, ) { const timestamp = Math.round(Date.now() / 1000); @@ -177,6 +177,10 @@ export class BackupController extends Controller { db_password: DB_PASSWORD, db_list: DB_LIST?.replaceAll(",", " "), }, + metadata: { + triggered_by: req.user.preferred_username, + triggered_by_id: req.user.sub, + }, }), }, ).then(async (r) => jsonParseOrPlainText(await r.text())); @@ -198,7 +202,10 @@ export class BackupController extends Controller { @Post("restore") @Security("keycloak") - async restoreBackup(@Body() body: { name: string }) { + async restoreBackup( + @Request() req: Request & { user: { sub: string; preferred_username: string } }, + @Body() body: { name: string }, + ) { const listRunning = await this.runningRestoreStatus(); if (!listRunning || listRunning.length > 0) { @@ -234,6 +241,10 @@ export class BackupController extends Controller { db_user: DB_USERNAME, db_password: DB_PASSWORD, }, + metadata: { + triggered_by: req.user.preferred_username, + triggered_by_id: req.user.sub, + }, }), }, ).then(async (r) => jsonParseOrPlainText(await r.text())); From c18ddd69859732767bc520ee53163899a9670965 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:07:04 +0700 Subject: [PATCH 5/9] refactor: change endpoint name --- src/controllers/backup-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/backup-controller.ts b/src/controllers/backup-controller.ts index ffe98ae..9c2ea7f 100644 --- a/src/controllers/backup-controller.ts +++ b/src/controllers/backup-controller.ts @@ -471,7 +471,7 @@ export class BackupController extends Controller { ).then(async (r) => jsonParseOrPlainText(await r.text())); } - @Post("notify-backup") + @Post("notify") async notifyBackup( @Body() payload: { message: string; userId?: string | string[]; roles?: string | string[] }, ) { From 2bbc3de04b00e5d3501b4a23dd118c0dac776e79 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:43:36 +0700 Subject: [PATCH 6/9] chore: update package --- nodemon.json | 2 +- package-lock.json | 209 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 3 files changed, 210 insertions(+), 2 deletions(-) diff --git a/nodemon.json b/nodemon.json index 0d5adab..bc1c53e 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,6 @@ { "exec": "tsoa spec-and-routes && ts-node src/app.ts", "ext": "ts", - "watch": ["src"], + "watch": ["src", ".env"], "ignore": ["src/routes.ts"] } diff --git a/package-lock.json b/package-lock.json index 983fe06..b5f011b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "minio": "^8.0.3", "promise.any": "^2.0.6", "reflect-metadata": "^0.2.2", + "socket.io": "^4.8.1", "swagger-ui-express": "^5.0.1", "tsoa": "^6.4.0" }, @@ -452,6 +453,12 @@ "node": ">=14" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -587,7 +594,6 @@ "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -892,6 +898,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1401,6 +1416,67 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -3228,6 +3304,116 @@ "node": ">=10" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3929,6 +4115,27 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", diff --git a/package.json b/package.json index 1ef582f..4e81daf 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "minio": "^8.0.3", "promise.any": "^2.0.6", "reflect-metadata": "^0.2.2", + "socket.io": "^4.8.1", "swagger-ui-express": "^5.0.1", "tsoa": "^6.4.0" } From a6704f3711d14d8d83d4077c83f0751e0480205b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:59:35 +0700 Subject: [PATCH 7/9] chore: update lock file --- pnpm-lock.yaml | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7787855..558df78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + socket.io: + specifier: ^4.8.1 + version: 4.8.1 swagger-ui-express: specifier: ^5.0.1 version: 5.0.1(express@4.19.2) @@ -198,6 +201,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@tsconfig/node10@1.0.9': resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -354,6 +360,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -433,6 +443,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -512,6 +526,14 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.6.4: + resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} + engines: {node: '>=10.2.0'} + es-abstract@1.22.3: resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} engines: {node: '>= 0.4'} @@ -1100,6 +1122,17 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -1309,6 +1342,18 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} @@ -1554,6 +1599,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@socket.io/component-emitter@3.1.2': {} + '@tsconfig/node10@1.0.9': {} '@tsconfig/node12@1.0.11': {} @@ -1753,6 +1800,8 @@ snapshots: balanced-match@1.0.2: {} + base64id@2.0.0: {} + binary-extensions@2.2.0: {} block-stream2@2.1.0: @@ -1849,6 +1898,8 @@ snapshots: cookie@0.6.0: {} + cookie@0.7.2: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -1914,6 +1965,24 @@ snapshots: encodeurl@1.0.2: {} + engine.io-parser@5.2.3: {} + + engine.io@6.6.4: + dependencies: + '@types/cors': 2.8.17 + '@types/node': 20.14.10 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.4(supports-color@5.5.0) + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + es-abstract@1.22.3: dependencies: array-buffer-byte-length: 1.0.0 @@ -2621,6 +2690,36 @@ snapshots: dependencies: semver: 7.5.4 + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.4(supports-color@5.5.0) + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.4(supports-color@5.5.0) + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + source-map@0.6.1: {} split-on-first@1.1.0: {} @@ -2849,6 +2948,8 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + ws@8.17.1: {} + xml2js@0.6.2: dependencies: sax: 1.4.1 From 386c18a1bc55475acad6787adfb0e0693c89c09a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:01:43 +0700 Subject: [PATCH 8/9] fix(ci): build failed --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e75f5a0..90e26ca 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-slim AS base +FROM node:22-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" @@ -16,7 +16,7 @@ FROM base AS build RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm run build -FROM base as prod +FROM base AS prod ENV NODE_ENV="production" COPY --from=deps /app/node_modules /app/node_modules COPY --from=build /app/dist /app/dist From f5b5f3751e40be1bd5bf19607a3b1328a676b2ff Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:20:46 +0700 Subject: [PATCH 9/9] feat: allow specify if notification is success or error --- src/controllers/backup-controller.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/controllers/backup-controller.ts b/src/controllers/backup-controller.ts index 9c2ea7f..6e8f6a5 100644 --- a/src/controllers/backup-controller.ts +++ b/src/controllers/backup-controller.ts @@ -473,11 +473,21 @@ export class BackupController extends Controller { @Post("notify") async notifyBackup( - @Body() payload: { message: string; userId?: string | string[]; roles?: string | string[] }, + @Body() + payload: { + message: string; + userId?: string | string[]; + roles?: string | string[]; + error?: boolean; + }, ) { - sendWebSocket("backup-notification", payload.message, { - roles: payload.roles || [], - userId: payload.userId || [], - }); + sendWebSocket( + "backup-notification", + { success: !payload.error, message: payload.message }, + { + roles: payload.roles || [], + userId: payload.userId || [], + }, + ); } }