From cdf243adca533a8a34f6bc4e69db24d21ac07220 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 12:13:29 +0100 Subject: [PATCH 01/27] Socket IO project & consultant basic --- backend/package-lock.json | 207 +++++++++++++++++- backend/package.json | 1 + backend/src/controllers/consultants.ts | 8 +- backend/src/controllers/projects.ts | 11 +- .../src/controllers/utils/entity-events.ts | 20 ++ backend/src/models/common.ts | 6 + backend/src/server.ts | 39 +++- backend/types/express/index.d.ts | 2 + frontend/package-lock.json | 141 ++++++++++++ frontend/package.json | 1 + frontend/src/actions/clientActions.ts | 2 + frontend/src/actions/consultantActions.ts | 63 ++++-- frontend/src/actions/initialLoad.ts | 4 + frontend/src/actions/invoiceActions.ts | 4 + frontend/src/actions/projectActions.ts | 40 +++- frontend/src/actions/userActions.ts | 3 + frontend/src/components/App.tsx | 1 + .../components/socketio/EntityEventPayload.ts | 6 + .../components/socketio/SocketEventTypes.ts | 5 + .../src/components/socketio/SocketService.ts | 60 +++++ frontend/src/index.tsx | 1 - frontend/src/routes.tsx | 2 + 22 files changed, 595 insertions(+), 32 deletions(-) create mode 100644 backend/src/controllers/utils/entity-events.ts create mode 100644 frontend/src/components/socketio/EntityEventPayload.ts create mode 100644 frontend/src/components/socketio/SocketEventTypes.ts create mode 100644 frontend/src/components/socketio/SocketService.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index a1da2fab..10e61684 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -34,6 +34,7 @@ "pug": "^2.0.4", "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", + "socket.io": "^4.8.1", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" @@ -3905,6 +3906,12 @@ "@sinonjs/commons": "^2.0.0" } }, + "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/@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -3992,6 +3999,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" + }, "node_modules/@types/cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", @@ -4002,7 +4015,6 @@ "version": "2.8.13", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -5452,6 +5464,15 @@ } ] }, + "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/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -6980,6 +7001,45 @@ "wrappy": "1" } }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.4.1", + "@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/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -16558,6 +16618,47 @@ "node": ">=0.10.0" } }, + "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-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/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -18720,6 +18821,27 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "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/xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", @@ -21810,6 +21932,11 @@ "@sinonjs/commons": "^2.0.0" } }, + "@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==" + }, "@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -21897,6 +22024,11 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "@types/cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", @@ -21907,7 +22039,6 @@ "version": "2.8.13", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dev": true, "requires": { "@types/node": "*" } @@ -23044,6 +23175,11 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -24225,6 +24361,35 @@ } } }, + "engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "requires": { + "@types/cookie": "^0.4.1", + "@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" + }, + "dependencies": { + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + } + } + }, + "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==" + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -31639,6 +31804,38 @@ } } }, + "socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "requires": { + "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" + } + }, + "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==", + "requires": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "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==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, "socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -33319,6 +33516,12 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "requires": {} + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 584753a8..3d3b73ba 100644 --- a/backend/package.json +++ b/backend/package.json @@ -52,6 +52,7 @@ "pug": "^2.0.4", "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", + "socket.io": "^4.8.1", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" diff --git a/backend/src/controllers/consultants.ts b/backend/src/controllers/consultants.ts index f9b4cae8..7b5ff5fd 100644 --- a/backend/src/controllers/consultants.ts +++ b/backend/src/controllers/consultants.ts @@ -2,9 +2,10 @@ import {Request, Response} from 'express'; import slugify from 'slugify'; import {ObjectID} from 'mongodb'; import {IConsultant} from '../models/consultants'; -import {CollectionNames, createAudit, updateAudit} from '../models/common'; +import {CollectionNames, createAudit, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import { emitEntityEvent } from './utils/entity-events'; export const getConsultants = async (req: Request, res: Response) => { const consultants = await req.db.collection(CollectionNames.CONSULTANTS).find().toArray(); @@ -20,7 +21,9 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { .findOneAndUpdate({_id: new ObjectID(_id)}, {$set: consultant}, {returnOriginal: true}); await saveAudit(req, 'consultant', originalConsultant, consultant); - return res.send({_id, ...consultant}); + const responseConsultant = {_id, ...consultant}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, responseConsultant, _id); + return res.send(responseConsultant); } const slug = slugify(`${consultant.firstName}-${consultant.name}`).toLowerCase(); @@ -31,5 +34,6 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdConsultant] = inserted.ops; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONSULTANTS, createdConsultant, createdConsultant._id); return res.send(createdConsultant); }; diff --git a/backend/src/controllers/projects.ts b/backend/src/controllers/projects.ts index 71533099..5465b3cc 100644 --- a/backend/src/controllers/projects.ts +++ b/backend/src/controllers/projects.ts @@ -2,9 +2,10 @@ import {Request, Response} from 'express'; import moment from 'moment'; import {ObjectID} from 'mongodb'; import {IProject} from '../models/projects'; -import {CollectionNames, createAudit, updateAudit} from '../models/common'; +import {CollectionNames, createAudit, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import {emitEntityEvent} from './utils/entity-events'; /** No longer in use: this is now done in the frontend */ export const findActiveProjectsForSelectedMonth = (selectedMonth: string, projects: IProject[]) => projects.filter(project => { @@ -44,7 +45,9 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { const projectsColl = req.db.collection(CollectionNames.PROJECTS); const {value: originalProject} = await projectsColl.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: project}, {returnOriginal: true}); await saveAudit(req, 'project', originalProject, project); - return res.send({_id, ...project}); + const responseProject = {_id, ...project}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS, responseProject, _id); + return res.send(responseProject); } const inserted = await req.db.collection>(CollectionNames.PROJECTS).insertOne({ @@ -52,12 +55,14 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdProject] = inserted.ops; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS, createdProject, createdProject._id); return res.send(createdProject); }; export const deleteProject = async (req: ConfacRequest, res: Response) => { const id = req.body.id; - await req.db.collection(CollectionNames.PROJECTS).findOneAndDelete({ _id: new ObjectID(id) }); + await req.db.collection(CollectionNames.PROJECTS).findOneAndDelete({_id: new ObjectID(id)}); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS, null, id); return res.send(id); }; diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts new file mode 100644 index 00000000..734aae3f --- /dev/null +++ b/backend/src/controllers/utils/entity-events.ts @@ -0,0 +1,20 @@ +import { ObjectID } from 'bson'; +import {CollectionNames, SocketEventTypes} from '../../models/common'; +import {ConfacRequest} from '../../models/technical'; + +export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entity:any, entityId: ObjectID) { + const sourceSocketId = req.headers['x-socket-id']; + req.io.emit(eventType, { + entityType, + entity, + entityId, + sourceSocketId, + } as EntityEventPayload); +} + +interface EntityEventPayload{ + entityType: string; + entity: any; + entityId: ObjectID; + sourceSocketId: string | undefined; +} \ No newline at end of file diff --git a/backend/src/models/common.ts b/backend/src/models/common.ts index f764b5d5..03416a89 100644 --- a/backend/src/models/common.ts +++ b/backend/src/models/common.ts @@ -25,6 +25,12 @@ export enum CollectionNames { ROLES = 'roles', } +export enum SocketEventTypes { + EntityCreated = 'ENTITY_CREATED', + EntityUpdated = 'ENTITY_UPDATED', + EntityDeleted = 'ENTITY_DELETED' +} + export interface IAudit { createdOn: string; createdBy: string; diff --git a/backend/src/server.ts b/backend/src/server.ts index 9372ebbd..ba3562e8 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -4,6 +4,8 @@ import bodyParser from 'body-parser'; import sgMail from '@sendgrid/mail'; import {MongoClient} from 'mongodb'; import cors from 'cors'; +import { Server } from 'socket.io'; +import http from 'http'; import 'express-async-errors'; @@ -11,18 +13,34 @@ import appConfig from './config'; import appRouter from './routes'; const app = express(); +const server = http.createServer(app); + +// TODO nicolas finetune CORS config... +const io = new Server(server, { + cors: { + origin: "*", // Allow all origins + methods: ["GET", "POST"], // Allowed HTTP methods + allowedHeaders: ["my-custom-header", "x-socket-id"], // Optional: specify allowed headers + credentials: true, // Allow credentials (e.g., cookies) + }, +}); sgMail.setApiKey(appConfig.SENDGRID_API_KEY); +// Allow only specific origins (e.g., your frontend's URL) +const corsOptions = { + origin: 'http://localhost:3000', // Replace with your frontend URL + methods: ['GET', 'POST', 'PUT', 'DELETE'], // Allowed HTTP methods + allowedHeaders: ['Content-Type', 'Authorization', 'x-socket-id'], // Allowed headers + credentials: true, // Allow cookies and credentials +}; -app.use(cors()); +app.use(cors(corsOptions)); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(express.static(`./${appConfig.server.basePath}public`)); - - if (appConfig.ENABLE_ROOT_TEMPLATES) { app.use(express.static('/templates')); } else { @@ -50,6 +68,7 @@ MongoClient.connect(connectionString, opts).then(client => { app.use((req: Request, res: Response, next: NextFunction) => { // TODO: fix race condition req.db = _MongoClient.db(); + req.io = io; next(); }); @@ -80,7 +99,19 @@ app.use((req: Request, res: Response) => res.sendFile('/home/public/index.html') -app.listen(appConfig.server.port, () => { +server.listen(appConfig.server.port, () => { console.log(`Server connected to port ${appConfig.server.port}, running in a ${appConfig.ENVIRONMENT} environment.`); console.log(appConfig); }); + + +// TODO nicolas remove debug below... +// Handle Socket.IO connections +io.on('connection', (socket) => { + console.log('A user connected'); + + // Disconnect event + socket.on('disconnect', () => { + console.log('A user disconnected'); + }); +}); diff --git a/backend/types/express/index.d.ts b/backend/types/express/index.d.ts index 55a68276..1a7a456b 100644 --- a/backend/types/express/index.d.ts +++ b/backend/types/express/index.d.ts @@ -1,9 +1,11 @@ import {Db} from 'mongodb'; +import { Server } from 'socket.io'; declare global { namespace Express { export interface Request { db: Db; + io: Server } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 01fc3c42..6f95cd61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,6 +41,7 @@ "redux": "^4.2.0", "redux-thunk": "^2.4.2", "reselect": "^4.1.7", + "socket.io-client": "^4.8.1", "superagent": "^8.0.3", "superagent-bluebird-promise": "^4.2.0", "use-debounce": "^8.0.4" @@ -4005,6 +4006,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "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/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -8467,6 +8474,49 @@ "node": ">= 0.8" } }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/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/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/enhanced-resolve": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", @@ -18546,6 +18596,34 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -20997,6 +21075,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -23934,6 +24020,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "@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==" + }, "@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -27375,6 +27466,31 @@ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true }, + "engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + }, + "dependencies": { + "ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "requires": {} + } + } + }, + "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==" + }, "enhanced-resolve": { "version": "5.12.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", @@ -34823,6 +34939,26 @@ } } }, + "socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + } + }, + "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==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -36713,6 +36849,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index f8a89408..694d70ed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "redux": "^4.2.0", "redux-thunk": "^2.4.2", "reselect": "^4.1.7", + "socket.io-client": "^4.8.1", "superagent": "^8.0.3", "superagent-bluebird-promise": "^4.2.0", "use-debounce": "^8.0.4" diff --git a/frontend/src/actions/clientActions.ts b/frontend/src/actions/clientActions.ts index a950443d..0bb08397 100644 --- a/frontend/src/actions/clientActions.ts +++ b/frontend/src/actions/clientActions.ts @@ -6,6 +6,7 @@ import t from '../trans'; import {ClientModel} from '../components/client/models/ClientModels'; import {busyToggle, success} from './appActions'; import {authService} from '../components/users/authService'; +import { socketService } from '../components/socketio/SocketService'; export function saveClient(client: ClientModel, stayOnPage = false, callback?: (client: ClientModel) => void) { @@ -14,6 +15,7 @@ export function saveClient(client: ClientModel, stayOnPage = false, callback?: ( return request.post(buildUrl('/clients')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(client) .then(res => { dispatch({ diff --git a/frontend/src/actions/consultantActions.ts b/frontend/src/actions/consultantActions.ts index cc0e6466..4f14acaf 100644 --- a/frontend/src/actions/consultantActions.ts +++ b/frontend/src/actions/consultantActions.ts @@ -1,29 +1,37 @@ -import request from 'superagent-bluebird-promise'; -import {catchHandler} from './utils/fetch'; -import {buildUrl} from './utils/buildUrl'; -import t from '../trans'; -import {ConsultantModel} from '../components/consultant/models/ConsultantModel'; -import {busyToggle, success} from './appActions'; -import {ACTION_TYPES} from './utils/ActionTypes'; -import {authService} from '../components/users/authService'; +import request from "superagent-bluebird-promise"; +import { catchHandler } from "./utils/fetch"; +import { buildUrl } from "./utils/buildUrl"; +import t from "../trans"; +import { ConsultantModel } from "../components/consultant/models/ConsultantModel"; +import { busyToggle, success } from "./appActions"; +import { ACTION_TYPES } from "./utils/ActionTypes"; +import { authService } from "../components/users/authService"; +import { socketService } from "../components/socketio/SocketService"; +import { EntityEventPayload } from "../components/socketio/EntityEventPayload"; +import { SocketEventTypes } from "../components/socketio/SocketEventTypes"; +import { Dispatch } from "redux"; - -export function saveConsultant(consultant: ConsultantModel, callback?: (savedConsultant: ConsultantModel) => void, navigate?: any) { - return dispatch => { +export function saveConsultant( + consultant: ConsultantModel, + callback?: (savedConsultant: ConsultantModel) => void, + navigate?: any +) { + return (dispatch) => { dispatch(busyToggle()); return request - .post(buildUrl('/consultants')) - .set('Content-Type', 'application/json') - .set('Authorization', authService.getBearer()) + .post(buildUrl("/consultants")) + .set("Content-Type", "application/json") + .set("Authorization", authService.getBearer()) + .set("x-socket-id", socketService.socketId) .send(consultant) - .then(response => { + .then((response) => { dispatch({ type: ACTION_TYPES.CONSULTANT_UPDATE, consultant: response.body, }); - success(t('config.popupMessage')); + success(t("config.popupMessage")); if (navigate) { - navigate('/consultants'); + navigate("/consultants"); } if (callback) { callback(response.body); @@ -33,3 +41,24 @@ export function saveConsultant(consultant: ConsultantModel, callback?: (savedCon .then(() => dispatch(busyToggle.off())); }; } + +export function handleConsultantSocketEvents( + eventType: string, + eventPayload: EntityEventPayload +) { + return (dispatch) => { + dispatch(busyToggle()); + switch (eventType) { + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.CONSULTANT_UPDATE, + consultant: eventPayload.entity, + }); + break; + default: + throw new Error(`${eventType} not supported for consultant.`); + } + dispatch(busyToggle.off()); + } +} diff --git a/frontend/src/actions/initialLoad.ts b/frontend/src/actions/initialLoad.ts index 5e928dfd..89e091f7 100644 --- a/frontend/src/actions/initialLoad.ts +++ b/frontend/src/actions/initialLoad.ts @@ -7,6 +7,7 @@ import {buildUrl} from './utils/buildUrl'; import {failure} from './appActions'; import {getProjectMonthsFilters} from '../reducers/app-state'; import {Features} from '../components/controls/feature/feature-models'; +import { socketService } from '../components/socketio/SocketService'; let counter: number; @@ -17,6 +18,9 @@ export const buildRequest = (url: string) => { if (authService.loggedIn()) { headers.append('Authorization', authService.getBearer()); } + if(socketService.socketId){ + headers.append('x-socket-id', socketService.socketId); + } const request = new Request(buildUrl(url), { method: 'GET', headers, diff --git a/frontend/src/actions/invoiceActions.ts b/frontend/src/actions/invoiceActions.ts index 0bb83018..5e993db4 100644 --- a/frontend/src/actions/invoiceActions.ts +++ b/frontend/src/actions/invoiceActions.ts @@ -7,6 +7,7 @@ import t from '../trans'; import InvoiceModel from '../components/invoice/models/InvoiceModel'; import {ProjectMonthModel} from '../components/project/models/ProjectMonthModel'; import {authService} from '../components/users/authService'; +import { socketService } from '../components/socketio/SocketService'; function cleanViewModel(data: InvoiceModel): InvoiceModel { @@ -24,6 +25,7 @@ export function createInvoice(data: InvoiceModel, navigate?: any) { request.post(buildUrl('/invoices')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .set('Accept', 'application/json') .send(cleanViewModel(data)) .then(res => { @@ -56,6 +58,7 @@ export function updateInvoiceRequest(data: InvoiceModel, successMsg: string | un request.put(buildUrl('/invoices')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .set('Accept', 'application/json') .send(cleanViewModel(data)) .then(res => { @@ -101,6 +104,7 @@ export function deleteInvoice(invoice: InvoiceModel) { request.delete(buildUrl('/invoices')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send({id: invoice._id}) .then(res => { console.log('invoice deleted', invoice); // eslint-disable-line diff --git a/frontend/src/actions/projectActions.ts b/frontend/src/actions/projectActions.ts index b28d0526..9cce4171 100644 --- a/frontend/src/actions/projectActions.ts +++ b/frontend/src/actions/projectActions.ts @@ -11,6 +11,9 @@ import {ProjectMonthModel} from '../components/project/models/ProjectMonthModel' import {TimesheetCheckAttachmentType} from '../models'; import {authService} from '../components/users/authService'; import {FullProjectMonthModel} from '../components/project/models/FullProjectMonthModel'; +import { socketService } from '../components/socketio/SocketService'; +import { EntityEventPayload } from '../components/socketio/EntityEventPayload'; +import { SocketEventTypes } from '../components/socketio/SocketEventTypes'; export function saveProject(project: IProjectModel, navigate?: any, after: 'to-list' | 'to-details' = 'to-list') { return (dispatch: Dispatch) => { @@ -19,6 +22,7 @@ export function saveProject(project: IProjectModel, navigate?: any, after: 'to-l .post(buildUrl('/projects')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(project) .then(response => { dispatch({ @@ -45,6 +49,7 @@ export function createProjectsMonth(month: Moment, projectIds: string[]) { .post(buildUrl('/projects/month')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send({month, projectIds}) .then(response => { dispatch({ @@ -64,6 +69,7 @@ export function createProjectsMonthInvoice(project: ProjectMonthModel) { .post(buildUrl(`/projects/month/${project._id}/create-invoice`)) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .then(response => { dispatch({ type: ACTION_TYPES.PROJECTS_MONTH_INVOICE_CREATED, @@ -84,6 +90,7 @@ export function deleteProjectsMonth(id: string, navigate: any) { request.delete(buildUrl('/projects/month')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send({id}) .then(res => { console.log('projectMonth deleted', id); // eslint-disable-line @@ -108,6 +115,7 @@ export function deleteProject(id: string, navigate: any) { request.delete(buildUrl('/projects')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send({id}) .then(res => { console.log('project deleted', id); // eslint-disable-line @@ -143,6 +151,7 @@ export function patchProjectsMonth(project: ProjectMonthModel) { .patch(buildUrl('/projects/month')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(project) .then(response => { dispatch({ @@ -163,7 +172,9 @@ export function projectMonthUpload(file: File, type: ProjectMonthAttachmentTypes const modelId = projectMonth.invoice ? projectMonth.invoice._id : projectMonth._id; const req = request .put(buildUrl(`/attachments/${modelType}/${modelId}/${type}`)) - .set('Authorization', authService.getBearer()); + .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) + ; req.attach(fileName, file); req.then(response => { @@ -189,7 +200,8 @@ export function projectsMonthOverviewUpload(file: File, month: Moment) { return (dispatch: Dispatch) => { const req = request .put(buildUrl(`/attachments/project_month_overview/${month.toISOString()}/${TimesheetCheckAttachmentType}`)) - .set('Authorization', authService.getBearer()); + .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId); req.attach(file.name, file); req.then(response => { @@ -208,7 +220,8 @@ export function deleteProjectsMonthOverview(id: string) { return (dispatch: Dispatch) => { const req = request .delete(buildUrl(`/attachments/project_month_overview/${id}/${TimesheetCheckAttachmentType}`)) - .set('Authorization', authService.getBearer()); + .set('Authorization', authService.getBearer()) + ; req.then(response => { dispatch({ @@ -230,3 +243,24 @@ export function deleteProjectMonthAttachmentDetails(projectMonth: ProjectMonthMo }); }; } + +export function handleProjectSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.PROJECT_UPDATE, + project: eventPayload.entity, + }); break; + case SocketEventTypes.EntityDeleted: + dispatch({ + type: ACTION_TYPES.PROJECT_DELETE, + id: eventPayload.entityId, + }); break; + default: throw new Error(`${eventType} not supported for project.`); + } + dispatch(busyToggle.off()); + } +} \ No newline at end of file diff --git a/frontend/src/actions/userActions.ts b/frontend/src/actions/userActions.ts index 0ada164c..1d85b78d 100644 --- a/frontend/src/actions/userActions.ts +++ b/frontend/src/actions/userActions.ts @@ -6,6 +6,7 @@ import {t} from '../components/utils'; import {catchHandler} from './utils/fetch'; import {busyToggle, success} from './appActions'; import {ACTION_TYPES} from './utils/ActionTypes'; +import { socketService } from '../components/socketio/SocketService'; export function saveUser(user: UserModel, callback?: (savedUser: UserModel) => void, navigate?: any) { @@ -15,6 +16,7 @@ export function saveUser(user: UserModel, callback?: (savedUser: UserModel) => v .put(buildUrl('/user')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(user) .then(response => { dispatch({ @@ -42,6 +44,7 @@ export function saveRole(role: RoleModel, callback?: (savedRole: RoleModel) => v .put(buildUrl('/user/roles')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(role) .then(response => { dispatch({ diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index 8a4a6d6c..9a05d2a8 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -4,6 +4,7 @@ import {ToastContainer} from 'react-toastify'; import Header from './Header'; import {ConfacState} from '../reducers/app-state'; + // import {success, failure} from '../actions/appActions'; // // diff --git a/frontend/src/components/socketio/EntityEventPayload.ts b/frontend/src/components/socketio/EntityEventPayload.ts new file mode 100644 index 00000000..79dc1b29 --- /dev/null +++ b/frontend/src/components/socketio/EntityEventPayload.ts @@ -0,0 +1,6 @@ +export interface EntityEventPayload { + entityType: string; + entity: any; + entityId: string; + sourceSocketId: string | undefined; +} diff --git a/frontend/src/components/socketio/SocketEventTypes.ts b/frontend/src/components/socketio/SocketEventTypes.ts new file mode 100644 index 00000000..58bfe2da --- /dev/null +++ b/frontend/src/components/socketio/SocketEventTypes.ts @@ -0,0 +1,5 @@ +export enum SocketEventTypes { + EntityCreated = 'ENTITY_CREATED', + EntityUpdated = 'ENTITY_UPDATED', + EntityDeleted = 'ENTITY_DELETED' +} \ No newline at end of file diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts new file mode 100644 index 00000000..01074e61 --- /dev/null +++ b/frontend/src/components/socketio/SocketService.ts @@ -0,0 +1,60 @@ + +import { AnyAction, Dispatch } from "redux"; +import { io } from "socket.io-client"; +import { handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { SocketEventTypes } from "./SocketEventTypes"; +import { EntityEventPayload } from "./EntityEventPayload"; + +function createSocketService () { + // TODO nicolas read server url from frontend config !!! + const socket = io('localhost:9000'); + + var socketId: undefined|string = undefined; + + socket.on('connect', () => { + console.log("Socketio: Connected to server"); + socketId = socket.id; + console.log(`SocketId: ${socketId}`); + }); + + function initialize(dispatch: Dispatch){ + socket.on(SocketEventTypes.EntityCreated, (msg)=>{ + handleEntityEvent(SocketEventTypes.EntityCreated, msg, dispatch); + }); + socket.on(SocketEventTypes.EntityUpdated, (msg)=>{ + handleEntityEvent(SocketEventTypes.EntityUpdated, msg, dispatch); + }); + socket.on(SocketEventTypes.EntityDeleted, (msg)=>{ + handleEntityEvent(SocketEventTypes.EntityDeleted, msg, dispatch); + }); + } + + function handleEntityEvent(eventType: string, eventPayload: EntityEventPayload, dispatch: Dispatch){ + console.log("HAndle entity event from socketio: " + eventType); + console.log("Source socket Id: " + eventPayload.sourceSocketId); + console.log("Payload:"); + console.log(eventPayload); + + if(eventPayload.sourceSocketId === socketId){ + console.log("Event ignored: sourceSocketId is equal to current socket id."); + return; + } + + switch(eventPayload.entityType){ + case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; + case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; + default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); + }; + } + + return { + get socketId() { + return socketId; + }, + initialize + }; +}; + + +export const socketService = createSocketService(); + diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index b75c14a3..d6cc2d0e 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -37,7 +37,6 @@ import {Provider} from 'react-redux'; import {store} from './store'; - // Create the AppRoot import Routes from './routes'; diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index bc60e61b..aec666b5 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -25,12 +25,14 @@ import {EditProjectMonths} from './components/project/EditProjectMonths'; import {ConsultantProjectsList} from './components/consultant/ConsultantProjectsList'; import {initialLoad} from './actions'; import {Home} from './components/home/Home'; +import { socketService } from './components/socketio/SocketService'; const Routes = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(initialLoad()); + socketService.initialize(dispatch); }, [dispatch]); return ( From d29839a1ee93dd638c579a2410a6b8bfeec23566 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 13:23:22 +0100 Subject: [PATCH 02/27] commit voor feedback --- .../src/controllers/utils/entity-events.ts | 3 ++ frontend/src/actions/appActions.tsx | 2 +- frontend/src/actions/consultantActions.ts | 4 +- .../components/socketio/EntityEventPayload.ts | 1 + .../src/components/socketio/SocketService.ts | 42 ++++++++++++++++--- frontend/src/trans.en.ts | 2 +- frontend/src/trans.nl.ts | 15 ++++++- 7 files changed, 58 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts index 734aae3f..89d43eaa 100644 --- a/backend/src/controllers/utils/entity-events.ts +++ b/backend/src/controllers/utils/entity-events.ts @@ -4,11 +4,13 @@ import {ConfacRequest} from '../../models/technical'; export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entity:any, entityId: ObjectID) { const sourceSocketId = req.headers['x-socket-id']; + const sourceUserEmail = req.user?.data?.email; req.io.emit(eventType, { entityType, entity, entityId, sourceSocketId, + sourceUserEmail, } as EntityEventPayload); } @@ -17,4 +19,5 @@ interface EntityEventPayload{ entity: any; entityId: ObjectID; sourceSocketId: string | undefined; + sourceUserEmail: string | undefined; } \ No newline at end of file diff --git a/frontend/src/actions/appActions.tsx b/frontend/src/actions/appActions.tsx index f3ad276b..fb044ffc 100644 --- a/frontend/src/actions/appActions.tsx +++ b/frontend/src/actions/appActions.tsx @@ -95,4 +95,4 @@ export function updateAppProjectMonthsFilter(month: string, opened: boolean) { type: ACTION_TYPES.APP_FILTER_OPEN_MONTHS_UPDATED, payload: {month, opened}, }; -} +} \ No newline at end of file diff --git a/frontend/src/actions/consultantActions.ts b/frontend/src/actions/consultantActions.ts index 4f14acaf..9712e1e4 100644 --- a/frontend/src/actions/consultantActions.ts +++ b/frontend/src/actions/consultantActions.ts @@ -6,7 +6,7 @@ import { ConsultantModel } from "../components/consultant/models/ConsultantModel import { busyToggle, success } from "./appActions"; import { ACTION_TYPES } from "./utils/ActionTypes"; import { authService } from "../components/users/authService"; -import { socketService } from "../components/socketio/SocketService"; +import { socketService, notifyEntityEvent } from "../components/socketio/SocketService"; import { EntityEventPayload } from "../components/socketio/EntityEventPayload"; import { SocketEventTypes } from "../components/socketio/SocketEventTypes"; import { Dispatch } from "redux"; @@ -59,6 +59,8 @@ export function handleConsultantSocketEvents( default: throw new Error(`${eventType} not supported for consultant.`); } + + notifyEntityEvent(eventPayload.entity.name, eventType, eventPayload); dispatch(busyToggle.off()); } } diff --git a/frontend/src/components/socketio/EntityEventPayload.ts b/frontend/src/components/socketio/EntityEventPayload.ts index 79dc1b29..f3f68843 100644 --- a/frontend/src/components/socketio/EntityEventPayload.ts +++ b/frontend/src/components/socketio/EntityEventPayload.ts @@ -3,4 +3,5 @@ export interface EntityEventPayload { entity: any; entityId: string; sourceSocketId: string | undefined; + sourceUserEmail: string | undefined; } diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 01074e61..33ffcaa3 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,15 +1,17 @@ import { AnyAction, Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { handleConsultantSocketEvents, handleProjectSocketEvents, info } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; +import { t } from "../utils"; function createSocketService () { // TODO nicolas read server url from frontend config !!! const socket = io('localhost:9000'); var socketId: undefined|string = undefined; + var initialized = false; socket.on('connect', () => { console.log("Socketio: Connected to server"); @@ -18,6 +20,10 @@ function createSocketService () { }); function initialize(dispatch: Dispatch){ + if(initialized){ + throw new Error("Initialize should only be called once."); + } + socket.on(SocketEventTypes.EntityCreated, (msg)=>{ handleEntityEvent(SocketEventTypes.EntityCreated, msg, dispatch); }); @@ -27,10 +33,12 @@ function createSocketService () { socket.on(SocketEventTypes.EntityDeleted, (msg)=>{ handleEntityEvent(SocketEventTypes.EntityDeleted, msg, dispatch); }); + + initialized = true; } - function handleEntityEvent(eventType: string, eventPayload: EntityEventPayload, dispatch: Dispatch){ - console.log("HAndle entity event from socketio: " + eventType); + function handleEntityEvent(eventType: SocketEventTypes, eventPayload: EntityEventPayload, dispatch: Dispatch){ + console.log("Received entity event from socketio: " + eventType); console.log("Source socket Id: " + eventPayload.sourceSocketId); console.log("Payload:"); console.log(eventPayload); @@ -41,10 +49,13 @@ function createSocketService () { } switch(eventPayload.entityType){ - case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; - case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; + case 'projects': + dispatch(handleProjectSocketEvents(eventType, eventPayload)); + break; + case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); + break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); - }; + }; } return { @@ -55,6 +66,25 @@ function createSocketService () { }; }; +export function notifyEntityEvent(entityDisplay: string, eventType: SocketEventTypes, eventPayload: EntityEventPayload){ + let operation = 'entityUpdated'; + + switch(eventType){ + case SocketEventTypes.EntityCreated: + operation = 'entityCreated';break; + case SocketEventTypes.EntityUpdated: + operation = 'entityUpdated';break; + case SocketEventTypes.EntityDeleted: + operation = 'entityDeleted'; break; + default: throw new Error(`${eventType} not supported.`); + } + + info(t(`socketio.operation.${operation}`, { + entityType: t(`socketio.entities.${eventPayload.entityType}`), + user: eventPayload.sourceUserEmail, + entityDisplay: entityDisplay + })) +} export const socketService = createSocketService(); diff --git a/frontend/src/trans.en.ts b/frontend/src/trans.en.ts index dcd748bc..b093551f 100644 --- a/frontend/src/trans.en.ts +++ b/frontend/src/trans.en.ts @@ -70,7 +70,7 @@ export const features = { name: 'Name', claims: 'Claims', }, - }, + } }; diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index df141ac8..0200d9ed 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -70,7 +70,7 @@ export const features = { name: 'Naam', claims: 'Claims', }, - }, + } }; @@ -676,5 +676,16 @@ export const trans = { } } } - } + }, + socketio: { + entities: { + projects: 'Project', + consultants: 'Consultant' + }, + operation: { + entityUpdated: '{entityType} {entityDisplay} werd aangepast door {user}', + entityCreated: '{entityType} {entityDisplay} werd aangemaakt door {user}', + entityDeleted: '{entityType} {entityDisplay} werd verwijderd door {user}' + } + } // TODO nicolas vertaling overnemen in .en file }; From fb19e7a4cda6a6d5f1f96ca2877582c969a6754e Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 14:06:35 +0100 Subject: [PATCH 03/27] current project updated, notification --- frontend/src/actions/consultantActions.ts | 3 +- .../src/components/project/EditProject.tsx | 14 +++- .../src/components/socketio/SocketService.ts | 72 +++++++++++++------ frontend/src/trans.nl.ts | 6 +- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/frontend/src/actions/consultantActions.ts b/frontend/src/actions/consultantActions.ts index 9712e1e4..14568af6 100644 --- a/frontend/src/actions/consultantActions.ts +++ b/frontend/src/actions/consultantActions.ts @@ -59,8 +59,7 @@ export function handleConsultantSocketEvents( default: throw new Error(`${eventType} not supported for consultant.`); } - - notifyEntityEvent(eventPayload.entity.name, eventType, eventPayload); + dispatch(busyToggle.off()); } } diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index edb58791..77c84553 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {Container, Row, Form} from 'react-bootstrap'; import {useNavigate} from 'react-router-dom'; @@ -24,6 +24,7 @@ import { SingleContractIcon } from "../client/contract/SingleContractIcon"; import {EnhanceWithConfirmation} from '../enhancers/EnhanceWithConfirmation'; import {Button} from '../controls/form-controls/Button'; import {isDateIntervalValid} from '../controls/other/ProjectValidator'; +import { socketService } from '../socketio/SocketService'; const ConfirmationButton = EnhanceWithConfirmation(Button); @@ -42,6 +43,17 @@ export const EditProject = () => { const hasProjectMonths = useSelector((state: ConfacState) => state.projectsMonth.some(pm => pm.projectId === params.id)); const [needsSync, setNeedsSync] = useState<{consultant: boolean, client: boolean}>({consultant: false, client: false}); + useEffect(()=>{ + var subs: undefined| (()=>void); + + if(model?._id){ + subs = socketService.enableNotificationsForEntity(model?._id); + } + + return subs; + + }, [model?._id]) + const docTitle = consultant._id ? 'projectEdit' : 'projectNew'; useDocumentTitle(docTitle, {consultant: consultant.firstName, client: client.name}); if (model && (!project._id || project._id !== params.id)) { diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 33ffcaa3..6da21666 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -5,6 +5,7 @@ import { handleConsultantSocketEvents, handleProjectSocketEvents, info } from ". import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; +import { toast } from "react-toastify"; function createSocketService () { // TODO nicolas read server url from frontend config !!! @@ -24,19 +25,48 @@ function createSocketService () { throw new Error("Initialize should only be called once."); } - socket.on(SocketEventTypes.EntityCreated, (msg)=>{ - handleEntityEvent(SocketEventTypes.EntityCreated, msg, dispatch); - }); - socket.on(SocketEventTypes.EntityUpdated, (msg)=>{ - handleEntityEvent(SocketEventTypes.EntityUpdated, msg, dispatch); - }); - socket.on(SocketEventTypes.EntityDeleted, (msg)=>{ - handleEntityEvent(SocketEventTypes.EntityDeleted, msg, dispatch); - }); + function registerEntityEventHandler(entityEventType: SocketEventTypes){ + socket.on(entityEventType,msg=> handleEntityEvent(entityEventType, msg, dispatch)); + } + + registerEntityEventHandler(SocketEventTypes.EntityCreated); + registerEntityEventHandler(SocketEventTypes.EntityUpdated); + registerEntityEventHandler(SocketEventTypes.EntityDeleted); initialized = true; } + function enableNotificationsForEntity(entityId: string ){ + + var unsubscriptions:(()=>void)[] = []; + + function registerEntityChangesSubscription(eventType: SocketEventTypes){ + var process = (msg: EntityEventPayload)=>{ + if(msg.sourceSocketId === socketId){ + console.log("Event ignored for entityId subscription => socket id is self"); + return; + } + if(msg.entityId !== entityId){ + console.log("Event ignored for entityId subscription => entity id not match"); + return; + } + notifyEntityEvent(eventType, msg); + }; + + socket.on(eventType, process); + + unsubscriptions.push(() => socket.off(eventType,process)); + } + + registerEntityChangesSubscription(SocketEventTypes.EntityCreated); + registerEntityChangesSubscription(SocketEventTypes.EntityUpdated); + registerEntityChangesSubscription(SocketEventTypes.EntityDeleted); + + return ()=> { + unsubscriptions.forEach(fn=> fn()); + } + } + function handleEntityEvent(eventType: SocketEventTypes, eventPayload: EntityEventPayload, dispatch: Dispatch){ console.log("Received entity event from socketio: " + eventType); console.log("Source socket Id: " + eventPayload.sourceSocketId); @@ -49,11 +79,8 @@ function createSocketService () { } switch(eventPayload.entityType){ - case 'projects': - dispatch(handleProjectSocketEvents(eventType, eventPayload)); - break; - case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); - break; + case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; + case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; } @@ -62,11 +89,12 @@ function createSocketService () { get socketId() { return socketId; }, - initialize + initialize, + enableNotificationsForEntity }; }; -export function notifyEntityEvent(entityDisplay: string, eventType: SocketEventTypes, eventPayload: EntityEventPayload){ +export function notifyEntityEvent(eventType: SocketEventTypes, eventPayload: EntityEventPayload){ let operation = 'entityUpdated'; switch(eventType){ @@ -79,11 +107,13 @@ export function notifyEntityEvent(entityDisplay: string, eventType: SocketEventT default: throw new Error(`${eventType} not supported.`); } - info(t(`socketio.operation.${operation}`, { - entityType: t(`socketio.entities.${eventPayload.entityType}`), - user: eventPayload.sourceUserEmail, - entityDisplay: entityDisplay - })) + toast.info( + t(`socketio.operation.${operation}`, { + entityType: t(`socketio.entities.${eventPayload.entityType}`), + user: eventPayload.sourceUserEmail, + }), + {autoClose: false, position: toast.POSITION.TOP_RIGHT, closeButton: true}, + ) } export const socketService = createSocketService(); diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index 0200d9ed..c54cbf79 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -683,9 +683,9 @@ export const trans = { consultants: 'Consultant' }, operation: { - entityUpdated: '{entityType} {entityDisplay} werd aangepast door {user}', - entityCreated: '{entityType} {entityDisplay} werd aangemaakt door {user}', - entityDeleted: '{entityType} {entityDisplay} werd verwijderd door {user}' + entityUpdated: '{entityType} werd aangepast door {user}', + entityCreated: '{entityType} werd aangemaakt door {user}', + entityDeleted: '{entityType} werd verwijderd door {user}' } } // TODO nicolas vertaling overnemen in .en file }; From cfee5ced636eef290cfc63dee252064ac16420ec Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 14:18:54 +0100 Subject: [PATCH 04/27] minorclean --- backend/src/controllers/consultants.ts | 4 +- backend/src/controllers/projects.ts | 6 +- .../src/controllers/utils/entity-events.ts | 2 +- .../src/components/project/EditProject.tsx | 2 +- .../src/components/socketio/SocketService.ts | 109 +++++++++--------- 5 files changed, 60 insertions(+), 63 deletions(-) diff --git a/backend/src/controllers/consultants.ts b/backend/src/controllers/consultants.ts index 7b5ff5fd..0b45f2f1 100644 --- a/backend/src/controllers/consultants.ts +++ b/backend/src/controllers/consultants.ts @@ -22,7 +22,7 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'consultant', originalConsultant, consultant); const responseConsultant = {_id, ...consultant}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, responseConsultant, _id); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, _id, responseConsultant); return res.send(responseConsultant); } @@ -34,6 +34,6 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdConsultant] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONSULTANTS, createdConsultant, createdConsultant._id); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONSULTANTS, createdConsultant._id, createdConsultant); return res.send(createdConsultant); }; diff --git a/backend/src/controllers/projects.ts b/backend/src/controllers/projects.ts index 5465b3cc..e2c1696e 100644 --- a/backend/src/controllers/projects.ts +++ b/backend/src/controllers/projects.ts @@ -46,7 +46,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { const {value: originalProject} = await projectsColl.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: project}, {returnOriginal: true}); await saveAudit(req, 'project', originalProject, project); const responseProject = {_id, ...project}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS, responseProject, _id); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS, _id, responseProject); return res.send(responseProject); } @@ -55,7 +55,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdProject] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS, createdProject, createdProject._id); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS, createdProject._id, createdProject); return res.send(createdProject); }; @@ -63,6 +63,6 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { export const deleteProject = async (req: ConfacRequest, res: Response) => { const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS).findOneAndDelete({_id: new ObjectID(id)}); - emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS, null, id); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS, id, null); return res.send(id); }; diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts index 89d43eaa..27f06b25 100644 --- a/backend/src/controllers/utils/entity-events.ts +++ b/backend/src/controllers/utils/entity-events.ts @@ -2,7 +2,7 @@ import { ObjectID } from 'bson'; import {CollectionNames, SocketEventTypes} from '../../models/common'; import {ConfacRequest} from '../../models/technical'; -export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entity:any, entityId: ObjectID) { +export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entityId: ObjectID, entity:any) { const sourceSocketId = req.headers['x-socket-id']; const sourceUserEmail = req.user?.data?.email; req.io.emit(eventType, { diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index 77c84553..129cfafc 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -47,7 +47,7 @@ export const EditProject = () => { var subs: undefined| (()=>void); if(model?._id){ - subs = socketService.enableNotificationsForEntity(model?._id); + subs = socketService.enableToastsForEntity(model?._id); } return subs; diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 6da21666..0c852764 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,7 +1,7 @@ -import { AnyAction, Dispatch } from "redux"; +import { Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleConsultantSocketEvents, handleProjectSocketEvents, info } from "../../actions"; +import { handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; @@ -20,27 +20,64 @@ function createSocketService () { console.log(`SocketId: ${socketId}`); }); - function initialize(dispatch: Dispatch){ + function initialize(dispatch: Dispatch){ if(initialized){ throw new Error("Initialize should only be called once."); } - function registerEntityEventHandler(entityEventType: SocketEventTypes){ - socket.on(entityEventType,msg=> handleEntityEvent(entityEventType, msg, dispatch)); + function registerEntityChangeEventHandler(entityEventType: SocketEventTypes, dispatch: Dispatch){ + socket.on(entityEventType, eventPayload=> { + console.log("Received entity event from socketio: " + entityEventType); + console.log("Source socket Id: " + eventPayload.sourceSocketId); + console.log("Payload:"); + console.log(eventPayload); + + if(eventPayload.sourceSocketId === socketId){ + console.log("Event ignored: sourceSocketId is equal to current socket id."); + return; + } + + switch(eventPayload.entityType){ + case 'projects': dispatch(handleProjectSocketEvents(entityEventType, eventPayload)); break; + case 'consultants': dispatch(handleConsultantSocketEvents(entityEventType, eventPayload)); break; + default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); + }; + }); } - - registerEntityEventHandler(SocketEventTypes.EntityCreated); - registerEntityEventHandler(SocketEventTypes.EntityUpdated); - registerEntityEventHandler(SocketEventTypes.EntityDeleted); + + registerEntityChangeEventHandler(SocketEventTypes.EntityCreated, dispatch); + registerEntityChangeEventHandler(SocketEventTypes.EntityUpdated, dispatch); + registerEntityChangeEventHandler(SocketEventTypes.EntityDeleted, dispatch); initialized = true; } - function enableNotificationsForEntity(entityId: string ){ + function toastEntityChanged(eventType: SocketEventTypes, eventPayload: EntityEventPayload){ + let operation = 'entityUpdated'; + + switch(eventType){ + case SocketEventTypes.EntityCreated: + operation = 'entityCreated';break; + case SocketEventTypes.EntityUpdated: + operation = 'entityUpdated';break; + case SocketEventTypes.EntityDeleted: + operation = 'entityDeleted'; break; + default: throw new Error(`${eventType} not supported.`); + } + + toast.info( + t(`socketio.operation.${operation}`, { + entityType: t(`socketio.entities.${eventPayload.entityType}`), + user: eventPayload.sourceUserEmail, + }), + {autoClose: false, position: toast.POSITION.TOP_RIGHT, closeButton: true}, + ); + } + function enableToastsForEntity(entityId: string){ var unsubscriptions:(()=>void)[] = []; - function registerEntityChangesSubscription(eventType: SocketEventTypes){ + function registerToastForEventType(eventType: SocketEventTypes){ var process = (msg: EntityEventPayload)=>{ if(msg.sourceSocketId === socketId){ console.log("Event ignored for entityId subscription => socket id is self"); @@ -50,7 +87,7 @@ function createSocketService () { console.log("Event ignored for entityId subscription => entity id not match"); return; } - notifyEntityEvent(eventType, msg); + toastEntityChanged(eventType, msg); }; socket.on(eventType, process); @@ -58,63 +95,23 @@ function createSocketService () { unsubscriptions.push(() => socket.off(eventType,process)); } - registerEntityChangesSubscription(SocketEventTypes.EntityCreated); - registerEntityChangesSubscription(SocketEventTypes.EntityUpdated); - registerEntityChangesSubscription(SocketEventTypes.EntityDeleted); + registerToastForEventType(SocketEventTypes.EntityCreated); + registerToastForEventType(SocketEventTypes.EntityUpdated); + registerToastForEventType(SocketEventTypes.EntityDeleted); return ()=> { unsubscriptions.forEach(fn=> fn()); } } - function handleEntityEvent(eventType: SocketEventTypes, eventPayload: EntityEventPayload, dispatch: Dispatch){ - console.log("Received entity event from socketio: " + eventType); - console.log("Source socket Id: " + eventPayload.sourceSocketId); - console.log("Payload:"); - console.log(eventPayload); - - if(eventPayload.sourceSocketId === socketId){ - console.log("Event ignored: sourceSocketId is equal to current socket id."); - return; - } - - switch(eventPayload.entityType){ - case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; - case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; - default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); - }; - } - return { get socketId() { return socketId; }, initialize, - enableNotificationsForEntity + enableToastsForEntity }; }; -export function notifyEntityEvent(eventType: SocketEventTypes, eventPayload: EntityEventPayload){ - let operation = 'entityUpdated'; - - switch(eventType){ - case SocketEventTypes.EntityCreated: - operation = 'entityCreated';break; - case SocketEventTypes.EntityUpdated: - operation = 'entityUpdated';break; - case SocketEventTypes.EntityDeleted: - operation = 'entityDeleted'; break; - default: throw new Error(`${eventType} not supported.`); - } - - toast.info( - t(`socketio.operation.${operation}`, { - entityType: t(`socketio.entities.${eventPayload.entityType}`), - user: eventPayload.sourceUserEmail, - }), - {autoClose: false, position: toast.POSITION.TOP_RIGHT, closeButton: true}, - ) -} - export const socketService = createSocketService(); From b8fd1f31516a4450e29f8af7845715c7c2f17e9d Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 15:45:07 +0100 Subject: [PATCH 05/27] hook --- backend/src/server.ts | 26 +++++-------------- frontend/src/actions/consultantActions.ts | 3 +-- .../components/consultant/EditConsultant.tsx | 6 ++++- .../hooks/useEntityChangedToast.tsx | 17 ++++++++++++ .../src/components/project/EditProject.tsx | 12 ++------- .../src/components/socketio/SocketService.ts | 10 +++---- 6 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 frontend/src/components/hooks/useEntityChangedToast.tsx diff --git a/backend/src/server.ts b/backend/src/server.ts index ba3562e8..91949459 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -4,7 +4,7 @@ import bodyParser from 'body-parser'; import sgMail from '@sendgrid/mail'; import {MongoClient} from 'mongodb'; import cors from 'cors'; -import { Server } from 'socket.io'; +import {Server} from 'socket.io'; import http from 'http'; import 'express-async-errors'; @@ -15,19 +15,19 @@ import appRouter from './routes'; const app = express(); const server = http.createServer(app); -// TODO nicolas finetune CORS config... -const io = new Server(server, { +// TODO nicolas finetune CORS config... +const io = new Server(server, { cors: { - origin: "*", // Allow all origins - methods: ["GET", "POST"], // Allowed HTTP methods - allowedHeaders: ["my-custom-header", "x-socket-id"], // Optional: specify allowed headers + origin: '*', // Allow all origins + methods: ['GET', 'POST'], // Allowed HTTP methods + allowedHeaders: ['x-socket-id'], // Optional: specify allowed headers credentials: true, // Allow credentials (e.g., cookies) }, }); sgMail.setApiKey(appConfig.SENDGRID_API_KEY); - +// TODO nicolas finetune CORS config... // Allow only specific origins (e.g., your frontend's URL) const corsOptions = { origin: 'http://localhost:3000', // Replace with your frontend URL @@ -103,15 +103,3 @@ server.listen(appConfig.server.port, () => { console.log(`Server connected to port ${appConfig.server.port}, running in a ${appConfig.ENVIRONMENT} environment.`); console.log(appConfig); }); - - -// TODO nicolas remove debug below... -// Handle Socket.IO connections -io.on('connection', (socket) => { - console.log('A user connected'); - - // Disconnect event - socket.on('disconnect', () => { - console.log('A user disconnected'); - }); -}); diff --git a/frontend/src/actions/consultantActions.ts b/frontend/src/actions/consultantActions.ts index 14568af6..27ad8dc4 100644 --- a/frontend/src/actions/consultantActions.ts +++ b/frontend/src/actions/consultantActions.ts @@ -6,10 +6,9 @@ import { ConsultantModel } from "../components/consultant/models/ConsultantModel import { busyToggle, success } from "./appActions"; import { ACTION_TYPES } from "./utils/ActionTypes"; import { authService } from "../components/users/authService"; -import { socketService, notifyEntityEvent } from "../components/socketio/SocketService"; +import { socketService } from "../components/socketio/SocketService"; import { EntityEventPayload } from "../components/socketio/EntityEventPayload"; import { SocketEventTypes } from "../components/socketio/SocketEventTypes"; -import { Dispatch } from "redux"; export function saveConsultant( consultant: ConsultantModel, diff --git a/frontend/src/components/consultant/EditConsultant.tsx b/frontend/src/components/consultant/EditConsultant.tsx index 7e2fb5b2..357c2acc 100644 --- a/frontend/src/components/consultant/EditConsultant.tsx +++ b/frontend/src/components/consultant/EditConsultant.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {Container, Row, Form, Alert} from 'react-bootstrap'; import {useNavigate} from 'react-router-dom'; @@ -15,6 +15,8 @@ import {useDocumentTitle} from '../hooks/useDocumentTitle'; import {Audit} from '../admin/audit/Audit'; import {Claim} from '../users/models/UserModel'; import {useParams} from 'react-router-dom'; +import { socketService } from '../socketio/SocketService'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; export const EditConsultant = () => { @@ -27,6 +29,8 @@ export const EditConsultant = () => { .filter(x => x.email === consultant.email) .find(x => x.slug !== params.id && x._id !== params.id)); + useEntityChangedToast(model?._id); + const docTitle = consultant._id ? 'consultantEdit' : 'consultantNew'; useDocumentTitle(docTitle, {name: `${consultant.firstName} ${consultant.name}`}); diff --git a/frontend/src/components/hooks/useEntityChangedToast.tsx b/frontend/src/components/hooks/useEntityChangedToast.tsx new file mode 100644 index 00000000..59269bf1 --- /dev/null +++ b/frontend/src/components/hooks/useEntityChangedToast.tsx @@ -0,0 +1,17 @@ +import { useEffect } from "react"; +import { socketService } from "../socketio/SocketService"; + +function useEntityChangedToast(entityId: string|null|undefined) { + useEffect(()=>{ + var subs: undefined| (()=>void); + + if(entityId){ + subs = socketService.enableToastsForEntity(entityId); + } + + return subs; + + }, [entityId]); +} + +export default useEntityChangedToast; \ No newline at end of file diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index 129cfafc..3cbe73d1 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -25,6 +25,7 @@ import {EnhanceWithConfirmation} from '../enhancers/EnhanceWithConfirmation'; import {Button} from '../controls/form-controls/Button'; import {isDateIntervalValid} from '../controls/other/ProjectValidator'; import { socketService } from '../socketio/SocketService'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; const ConfirmationButton = EnhanceWithConfirmation(Button); @@ -43,16 +44,7 @@ export const EditProject = () => { const hasProjectMonths = useSelector((state: ConfacState) => state.projectsMonth.some(pm => pm.projectId === params.id)); const [needsSync, setNeedsSync] = useState<{consultant: boolean, client: boolean}>({consultant: false, client: false}); - useEffect(()=>{ - var subs: undefined| (()=>void); - - if(model?._id){ - subs = socketService.enableToastsForEntity(model?._id); - } - - return subs; - - }, [model?._id]) + useEntityChangedToast(model?._id); const docTitle = consultant._id ? 'projectEdit' : 'projectNew'; useDocumentTitle(docTitle, {consultant: consultant.firstName, client: client.name}); diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 0c852764..52115dd2 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -53,13 +53,13 @@ function createSocketService () { } function toastEntityChanged(eventType: SocketEventTypes, eventPayload: EntityEventPayload){ - let operation = 'entityUpdated'; + let operation: string | undefined; switch(eventType){ case SocketEventTypes.EntityCreated: - operation = 'entityCreated';break; + operation = 'entityCreated'; break; case SocketEventTypes.EntityUpdated: - operation = 'entityUpdated';break; + operation = 'entityUpdated'; break; case SocketEventTypes.EntityDeleted: operation = 'entityDeleted'; break; default: throw new Error(`${eventType} not supported.`); @@ -74,8 +74,8 @@ function createSocketService () { ); } - function enableToastsForEntity(entityId: string){ - var unsubscriptions:(()=>void)[] = []; + function enableToastsForEntity(entityId: string) { + var unsubscriptions: (()=>void)[] = []; function registerToastForEventType(eventType: SocketEventTypes){ var process = (msg: EntityEventPayload)=>{ From 7e6bbfac4e9d248725afaf289abf3695a71fb710 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 16:18:41 +0100 Subject: [PATCH 06/27] client --- backend/src/controllers/clients.ts | 8 +++-- frontend/src/actions/clientActions.ts | 19 ++++++++++ frontend/src/components/client/EditClient.tsx | 2 ++ .../src/components/socketio/SocketService.ts | 36 ++++++++++--------- frontend/src/trans.nl.ts | 3 +- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/backend/src/controllers/clients.ts b/backend/src/controllers/clients.ts index ba1f696b..41f4cbc4 100644 --- a/backend/src/controllers/clients.ts +++ b/backend/src/controllers/clients.ts @@ -3,9 +3,10 @@ import slugify from 'slugify'; import fetch from 'node-fetch'; import {ObjectID} from 'mongodb'; import {IClient} from '../models/clients'; -import {CollectionNames, updateAudit, createAudit} from '../models/common'; +import {CollectionNames, updateAudit, createAudit, SocketEventTypes} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import {emitEntityEvent} from './utils/entity-events'; export const getClients = async (req: Request, res: Response) => { @@ -31,7 +32,9 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { const {value: originalClient} = await clientsCollection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: client}, {returnOriginal: true}); await saveAudit(req, 'client', originalClient, client); - return res.send({_id, ...client}); + const clientResponse = {_id, ...client}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CLIENTS, _id, clientResponse); + return res.send(clientResponse); } @@ -39,5 +42,6 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { client.audit = createAudit(req.user); const inserted = await req.db.collection(CollectionNames.CLIENTS).insertOne(client); const [createdClient] = inserted.ops; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CLIENTS, createdClient._id, createdClient); return res.send(createdClient); }; diff --git a/frontend/src/actions/clientActions.ts b/frontend/src/actions/clientActions.ts index 0bb08397..0faeaa14 100644 --- a/frontend/src/actions/clientActions.ts +++ b/frontend/src/actions/clientActions.ts @@ -7,6 +7,9 @@ import {ClientModel} from '../components/client/models/ClientModels'; import {busyToggle, success} from './appActions'; import {authService} from '../components/users/authService'; import { socketService } from '../components/socketio/SocketService'; +import { EntityEventPayload } from '../components/socketio/EntityEventPayload'; +import { SocketEventTypes } from '../components/socketio/SocketEventTypes'; +import { Dispatch } from 'redux'; export function saveClient(client: ClientModel, stayOnPage = false, callback?: (client: ClientModel) => void) { @@ -34,3 +37,19 @@ export function saveClient(client: ClientModel, stayOnPage = false, callback?: ( .then(() => dispatch(busyToggle.off())); }; } + +export function handleClientSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.CLIENT_UPDATE, + client: eventPayload.entity, + }); break; + default: throw new Error(`${eventType} not supported for client.`); + } + dispatch(busyToggle.off()); + } +} \ No newline at end of file diff --git a/frontend/src/components/client/EditClient.tsx b/frontend/src/components/client/EditClient.tsx index 3ad6c0a5..5f36fa39 100644 --- a/frontend/src/components/client/EditClient.tsx +++ b/frontend/src/components/client/EditClient.tsx @@ -18,6 +18,7 @@ import {Audit} from '../admin/audit/Audit'; import {Claim} from '../users/models/UserModel'; import {useParams} from 'react-router-dom'; import {InvoiceLine} from '../invoice/models/InvoiceLineModels'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; /** Different spellings of "Belgium" */ @@ -42,6 +43,7 @@ const EditClient = () => { const dispatch = useDispatch(); // useEffect(() => window.scrollTo(0, 0)); // TODO: each keystroke made it scroll to top :( useDocumentTitle('clientEdit', {name: client.name}); + useEntityChangedToast(client._id); if (storeClient && !client._id) { setClient(storeClient); diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 52115dd2..2c827313 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,7 +1,7 @@ import { Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { handleClientSocketEvents, handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; @@ -25,9 +25,9 @@ function createSocketService () { throw new Error("Initialize should only be called once."); } - function registerEntityChangeEventHandler(entityEventType: SocketEventTypes, dispatch: Dispatch){ - socket.on(entityEventType, eventPayload=> { - console.log("Received entity event from socketio: " + entityEventType); + function registerHandlerForEventType(eventType: SocketEventTypes, dispatch: Dispatch){ + socket.on(eventType, eventPayload=> { + console.log("Received entity event from socketio: " + eventType); console.log("Source socket Id: " + eventPayload.sourceSocketId); console.log("Payload:"); console.log(eventPayload); @@ -38,16 +38,17 @@ function createSocketService () { } switch(eventPayload.entityType){ - case 'projects': dispatch(handleProjectSocketEvents(entityEventType, eventPayload)); break; - case 'consultants': dispatch(handleConsultantSocketEvents(entityEventType, eventPayload)); break; + case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; + case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; + case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); } - registerEntityChangeEventHandler(SocketEventTypes.EntityCreated, dispatch); - registerEntityChangeEventHandler(SocketEventTypes.EntityUpdated, dispatch); - registerEntityChangeEventHandler(SocketEventTypes.EntityDeleted, dispatch); + registerHandlerForEventType(SocketEventTypes.EntityCreated, dispatch); + registerHandlerForEventType(SocketEventTypes.EntityUpdated, dispatch); + registerHandlerForEventType(SocketEventTypes.EntityDeleted, dispatch); initialized = true; } @@ -77,10 +78,11 @@ function createSocketService () { function enableToastsForEntity(entityId: string) { var unsubscriptions: (()=>void)[] = []; - function registerToastForEventType(eventType: SocketEventTypes){ - var process = (msg: EntityEventPayload)=>{ + function registerHandlerForEventType(eventType: SocketEventTypes){ + + var handleEvent = (msg: EntityEventPayload)=>{ if(msg.sourceSocketId === socketId){ - console.log("Event ignored for entityId subscription => socket id is self"); + console.log("Event ignored for entityId subscription => source socket id is self"); return; } if(msg.entityId !== entityId){ @@ -90,14 +92,14 @@ function createSocketService () { toastEntityChanged(eventType, msg); }; - socket.on(eventType, process); + socket.on(eventType, handleEvent); - unsubscriptions.push(() => socket.off(eventType,process)); + unsubscriptions.push(() => socket.off(eventType,handleEvent)); } - registerToastForEventType(SocketEventTypes.EntityCreated); - registerToastForEventType(SocketEventTypes.EntityUpdated); - registerToastForEventType(SocketEventTypes.EntityDeleted); + registerHandlerForEventType(SocketEventTypes.EntityCreated); + registerHandlerForEventType(SocketEventTypes.EntityUpdated); + registerHandlerForEventType(SocketEventTypes.EntityDeleted); return ()=> { unsubscriptions.forEach(fn=> fn()); diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index c54cbf79..167cd099 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -680,7 +680,8 @@ export const trans = { socketio: { entities: { projects: 'Project', - consultants: 'Consultant' + consultants: 'Consultant', + clients: 'Klant' }, operation: { entityUpdated: '{entityType} werd aangepast door {user}', From cd0aeff7dd0ac61f5fac466c0f0f91304b364fad Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 16:30:23 +0100 Subject: [PATCH 07/27] users --- backend/src/controllers/user.ts | 12 ++++++++---- frontend/src/actions/userActions.ts | 19 +++++++++++++++++++ frontend/src/components/client/EditClient.tsx | 5 ++++- .../src/components/project/EditProject.tsx | 2 +- .../src/components/socketio/SocketService.ts | 2 ++ frontend/src/components/users/EditUser.tsx | 6 +++++- frontend/src/trans.nl.ts | 3 ++- 7 files changed, 41 insertions(+), 8 deletions(-) diff --git a/backend/src/controllers/user.ts b/backend/src/controllers/user.ts index 00883e8c..78d7c250 100644 --- a/backend/src/controllers/user.ts +++ b/backend/src/controllers/user.ts @@ -3,11 +3,12 @@ import {Request, Response} from 'express'; import {ObjectID} from 'mongodb'; import {OAuth2Client, TokenPayload} from 'google-auth-library'; import jwt from 'jsonwebtoken'; -import {CollectionNames, IAudit, createAudit, updateAudit} from '../models/common'; +import {CollectionNames, IAudit, SocketEventTypes, createAudit, updateAudit} from '../models/common'; import config from '../config'; import {IUser, IRole} from '../models/user'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import { emitEntityEvent } from './utils/entity-events'; const AdminRole = 'admin'; @@ -111,13 +112,16 @@ export const saveUser = async (req: ConfacRequest, res: Response) => { user.audit = updateAudit(user.audit, req.user); const {value: originalUser} = await collection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: user}, {returnOriginal: true}); await saveAudit(req, 'user', originalUser, user); - return res.send({_id, ...user}); + const responseUser = {_id, ...user}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.USERS, _id, responseUser); + return res.send(responseUser); } user.audit = createAudit(req.user); const inserted = await collection.insertOne(user); - const [createdClient] = inserted.ops; - return res.send(createdClient); + const [createdUser] = inserted.ops; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.USERS, createdUser._id, createdUser); + return res.send(createdUser); }; diff --git a/frontend/src/actions/userActions.ts b/frontend/src/actions/userActions.ts index 1d85b78d..8c2f2591 100644 --- a/frontend/src/actions/userActions.ts +++ b/frontend/src/actions/userActions.ts @@ -7,6 +7,9 @@ import {catchHandler} from './utils/fetch'; import {busyToggle, success} from './appActions'; import {ACTION_TYPES} from './utils/ActionTypes'; import { socketService } from '../components/socketio/SocketService'; +import { SocketEventTypes } from '../components/socketio/SocketEventTypes'; +import { Dispatch } from 'redux'; +import { EntityEventPayload } from '../components/socketio/EntityEventPayload'; export function saveUser(user: UserModel, callback?: (savedUser: UserModel) => void, navigate?: any) { @@ -36,6 +39,22 @@ export function saveUser(user: UserModel, callback?: (savedUser: UserModel) => v }; } +export function handleUserSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.USER_UPDATE, + user: eventPayload.entity, + }); break; + default: throw new Error(`${eventType} not supported for user.`); + } + dispatch(busyToggle.off()); + } +} + export function saveRole(role: RoleModel, callback?: (savedRole: RoleModel) => void, navigate?: any) { return dispatch => { diff --git a/frontend/src/components/client/EditClient.tsx b/frontend/src/components/client/EditClient.tsx index 5f36fa39..5b64d001 100644 --- a/frontend/src/components/client/EditClient.tsx +++ b/frontend/src/components/client/EditClient.tsx @@ -40,10 +40,13 @@ const EditClient = () => { const initClient = getClient(storeClient, config); const [client, setClient] = useState(initClient); const clientWithSameKbo = useSelector((state: ConfacState) => state.clients.filter(x => x.btw === client.btw).find(x => x.slug !== params.id)); + + useEntityChangedToast(client._id); + const dispatch = useDispatch(); // useEffect(() => window.scrollTo(0, 0)); // TODO: each keystroke made it scroll to top :( useDocumentTitle('clientEdit', {name: client.name}); - useEntityChangedToast(client._id); + if (storeClient && !client._id) { setClient(storeClient); diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index 3cbe73d1..7726b41d 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -43,7 +43,7 @@ export const EditProject = () => { const client = useSelector((state: ConfacState) => state.clients.find(x => x._id === project.client.clientId) || getNewClient()); const hasProjectMonths = useSelector((state: ConfacState) => state.projectsMonth.some(pm => pm.projectId === params.id)); const [needsSync, setNeedsSync] = useState<{consultant: boolean, client: boolean}>({consultant: false, client: false}); - + useEntityChangedToast(model?._id); const docTitle = consultant._id ? 'projectEdit' : 'projectNew'; diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 2c827313..5ffc2d79 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -6,6 +6,7 @@ import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; import { toast } from "react-toastify"; +import { handleUserSocketEvents } from "../../actions/userActions"; function createSocketService () { // TODO nicolas read server url from frontend config !!! @@ -41,6 +42,7 @@ function createSocketService () { case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; + case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); diff --git a/frontend/src/components/users/EditUser.tsx b/frontend/src/components/users/EditUser.tsx index 962ed6bd..5e5d24c5 100644 --- a/frontend/src/components/users/EditUser.tsx +++ b/frontend/src/components/users/EditUser.tsx @@ -14,6 +14,7 @@ import {useDocumentTitle} from '../hooks/useDocumentTitle'; import {saveUser} from '../../actions/userActions'; import {Audit} from '../admin/audit/Audit'; import {useParams} from 'react-router-dom'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; @@ -23,9 +24,12 @@ export const EditUser = () => { const params = useParams(); const model = useSelector((state: ConfacState) => state.user.users.find(c => c.alias === params.id)); const [user, setUser] = useState(model || getNewUser()); - + + useEntityChangedToast(user._id); + const docTitle = user._id ? 'userEdit' : 'userNew'; useDocumentTitle(docTitle, {name: `${user.firstName} ${user.name}`}); + if (model && !user._id) { setUser(model); diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index 167cd099..091ec01e 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -681,7 +681,8 @@ export const trans = { entities: { projects: 'Project', consultants: 'Consultant', - clients: 'Klant' + clients: 'Klant', + users: 'Gebruiker' }, operation: { entityUpdated: '{entityType} werd aangepast door {user}', From c83438ec36f263b8d5cd45c0e43a206a70a9b538 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 16:34:30 +0100 Subject: [PATCH 08/27] role --- backend/src/controllers/user.ts | 10 +++++++--- frontend/src/actions/userActions.ts | 16 ++++++++++++++++ .../src/components/socketio/SocketService.ts | 3 ++- frontend/src/components/users/EditRole.tsx | 3 +++ frontend/src/trans.nl.ts | 3 ++- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/user.ts b/backend/src/controllers/user.ts index 78d7c250..e8f3a1cd 100644 --- a/backend/src/controllers/user.ts +++ b/backend/src/controllers/user.ts @@ -146,11 +146,15 @@ export const saveRole = async (req: ConfacRequest, res: Response) => { role.audit = updateAudit(role.audit, req.user); const {value: originalRole} = await collection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: role}, {returnOriginal: true}); await saveAudit(req, 'role', originalRole, role); - return res.send({_id, ...role}); + + const responseRole = {_id, ...role}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.ROLES, _id, responseRole); + return res.send(responseRole); } role.audit = createAudit(req.user); const inserted = await collection.insertOne(role); - const [createdClient] = inserted.ops; - return res.send(createdClient); + const [createdRole] = inserted.ops; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.ROLES, createdRole._id, createdRole); + return res.send(createdRole); }; diff --git a/frontend/src/actions/userActions.ts b/frontend/src/actions/userActions.ts index 8c2f2591..09635155 100644 --- a/frontend/src/actions/userActions.ts +++ b/frontend/src/actions/userActions.ts @@ -82,3 +82,19 @@ export function saveRole(role: RoleModel, callback?: (savedRole: RoleModel) => v .then(() => dispatch(busyToggle.off())); }; } + +export function handleRoleSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.ROLE_UPDATE, + role: eventPayload.entity, + }); break; + default: throw new Error(`${eventType} not supported for role.`); + } + dispatch(busyToggle.off()); +} +} diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 5ffc2d79..1c2b163a 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -6,7 +6,7 @@ import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; import { toast } from "react-toastify"; -import { handleUserSocketEvents } from "../../actions/userActions"; +import { handleRoleSocketEvents, handleUserSocketEvents } from "../../actions/userActions"; function createSocketService () { // TODO nicolas read server url from frontend config !!! @@ -43,6 +43,7 @@ function createSocketService () { case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; + case 'roles': dispatch(handleRoleSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); diff --git a/frontend/src/components/users/EditRole.tsx b/frontend/src/components/users/EditRole.tsx index 3c1686c9..66ac1b9c 100644 --- a/frontend/src/components/users/EditRole.tsx +++ b/frontend/src/components/users/EditRole.tsx @@ -14,6 +14,7 @@ import {useDocumentTitle} from '../hooks/useDocumentTitle'; import {saveRole} from '../../actions/userActions'; import {Audit} from '../admin/audit/Audit'; import {useParams} from 'react-router-dom'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; export const EditRole = () => { @@ -23,6 +24,8 @@ export const EditRole = () => { const model = useSelector((state: ConfacState) => state.user.roles.find(c => c.name === params.id)); const [role, setRole] = useState(model || getNewRole()); + useEntityChangedToast(role._id); + const docTitle = role._id ? 'roleEdit' : 'roleNew'; useDocumentTitle(docTitle, {name: role.name}); diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index 091ec01e..495e092b 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -682,7 +682,8 @@ export const trans = { projects: 'Project', consultants: 'Consultant', clients: 'Klant', - users: 'Gebruiker' + users: 'Gebruiker', + roles: 'Rol' }, operation: { entityUpdated: '{entityType} werd aangepast door {user}', From fc98fdef060861482b335e0ce17608092bf32abd Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 16:44:51 +0100 Subject: [PATCH 09/27] config --- backend/src/controllers/config.ts | 11 +++++++--- frontend/src/actions/configActions.ts | 21 +++++++++++++++++++ frontend/src/components/config/EditConfig.tsx | 3 +++ .../src/components/socketio/SocketService.ts | 3 ++- frontend/src/trans.nl.ts | 3 ++- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/config.ts b/backend/src/controllers/config.ts index a06f1876..5368712c 100644 --- a/backend/src/controllers/config.ts +++ b/backend/src/controllers/config.ts @@ -4,9 +4,10 @@ import {ObjectID} from 'mongodb'; import appConfig from '../config'; import {ICompanyConfig} from '../models/config'; import {getTemplatesPath} from './utils'; -import {CollectionNames, updateAudit} from '../models/common'; +import {CollectionNames, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import { emitEntityEvent } from './utils/entity-events'; export const getCompanyConfig = async (req: Request, res: Response) => { const companyConfig: ICompanyConfig | null = await req.db.collection(CollectionNames.CONFIG).findOne({key: 'conf'}); @@ -41,11 +42,15 @@ export const saveCompanyConfig = async (req: ConfacRequest, res: Response) => { .findOneAndUpdate({_id: new ObjectID(_id)}, {$set: config}, {returnOriginal: true}); await saveAudit(req, 'config', originalConfig, config); - return res.send({_id, ...config}); + const responseConfig = {_id, ...config}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONFIG, _id, responseConfig); + return res.send(responseConfig); } const inserted = await req.db.collection(CollectionNames.CONFIG).insertOne(config); - return res.send(inserted.ops[0]); + const responseConfig = inserted.ops[0]; + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONFIG, responseConfig._id, responseConfig); + return res.send(responseConfig); }; diff --git a/frontend/src/actions/configActions.ts b/frontend/src/actions/configActions.ts index aae5c3e7..7e38edf4 100644 --- a/frontend/src/actions/configActions.ts +++ b/frontend/src/actions/configActions.ts @@ -6,6 +6,10 @@ import t from '../trans'; import {ConfigModel} from '../components/config/models/ConfigModel'; import {busyToggle, success} from './appActions'; import {authService} from '../components/users/authService'; +import { Dispatch } from 'redux'; +import { EntityEventPayload } from '../components/socketio/EntityEventPayload'; +import { SocketEventTypes } from '../components/socketio/SocketEventTypes'; +import { socketService } from '../components/socketio/SocketService'; export function updateConfig(newConfig: ConfigModel) { return dispatch => { @@ -13,6 +17,7 @@ export function updateConfig(newConfig: ConfigModel) { return request.post(buildUrl('/config')) .set('Content-Type', 'application/json') .set('Authorization', authService.getBearer()) + .set('x-socket-id', socketService.socketId) .send(newConfig) .then(res => { dispatch({type: ACTION_TYPES.CONFIG_UPDATE, config: res.body}); @@ -22,3 +27,19 @@ export function updateConfig(newConfig: ConfigModel) { .then(() => dispatch(busyToggle.off())); }; } + +export function handleconfigSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.CONFIG_UPDATE, + config: eventPayload.entity, + }); break; + default: throw new Error(`${eventType} not supported for config.`); + } + dispatch(busyToggle.off()); + } +} diff --git a/frontend/src/components/config/EditConfig.tsx b/frontend/src/components/config/EditConfig.tsx index faee3e03..8db005de 100644 --- a/frontend/src/components/config/EditConfig.tsx +++ b/frontend/src/components/config/EditConfig.tsx @@ -14,6 +14,7 @@ import {Audit} from '../admin/audit/Audit'; import {Claim} from '../users/models/UserModel'; import {GenericAttachmentDropzone} from '../controls/attachments/GenericAttachmentDropzone'; import {ClaimGuard} from '../enhancers/EnhanceWithClaim'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; const EditConfig = () => { @@ -22,6 +23,8 @@ const EditConfig = () => { const config = useSelector((state: ConfacState) => state.config); const [state, setState] = useState(JSON.parse(JSON.stringify(config))); + useEntityChangedToast(config._id); + const termsAndConditions = config.attachments.find(x => x.type === 'TermsAndConditions'); return ( diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 1c2b163a..c7d9576c 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,7 +1,7 @@ import { Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleClientSocketEvents, handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { handleClientSocketEvents, handleconfigSocketEvents, handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; @@ -44,6 +44,7 @@ function createSocketService () { case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; case 'roles': dispatch(handleRoleSocketEvents(eventType, eventPayload)); break; + case 'config': dispatch(handleconfigSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index 495e092b..fe033c9c 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -683,7 +683,8 @@ export const trans = { consultants: 'Consultant', clients: 'Klant', users: 'Gebruiker', - roles: 'Rol' + roles: 'Rol', + config: 'Configuratie' }, operation: { entityUpdated: '{entityType} werd aangepast door {user}', From 1155ceee2e20e4268bddc4d376c19efde728857e Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 30 Dec 2024 17:43:51 +0100 Subject: [PATCH 10/27] project month wip --- backend/src/controllers/projectsMonth.ts | 12 ++++++-- .../src/controllers/utils/entity-events.ts | 4 +-- backend/src/server.ts | 4 +-- frontend/src/actions/configActions.ts | 2 +- frontend/src/actions/projectActions.ts | 29 +++++++++++++++++++ .../hooks/useEntityChangedToast.tsx | 8 ++--- .../components/project/EditProjectMonths.tsx | 3 ++ .../project-month-list/ProjectMonthsLists.tsx | 3 ++ .../components/socketio/EntityEventPayload.ts | 2 +- .../src/components/socketio/SocketService.ts | 14 ++++++--- frontend/src/trans.nl.ts | 3 +- 11 files changed, 66 insertions(+), 18 deletions(-) diff --git a/backend/src/controllers/projectsMonth.ts b/backend/src/controllers/projectsMonth.ts index d5e51fd3..92a6ff5b 100644 --- a/backend/src/controllers/projectsMonth.ts +++ b/backend/src/controllers/projectsMonth.ts @@ -2,9 +2,10 @@ import {Request, Response} from 'express'; import {ObjectID} from 'mongodb'; import moment from 'moment'; import {IProjectMonth, IProjectMonthOverview, TimesheetCheckAttachmentType} from '../models/projectsMonth'; -import {CollectionNames, createAudit, updateAudit} from '../models/common'; +import {CollectionNames, createAudit, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import { emitEntityEvent } from './utils/entity-events'; export const getProjectsPerMonthController = async (req: Request, res: Response) => { @@ -55,6 +56,8 @@ export const createProjectsMonthController = async (req: ConfacRequest, res: Res return createdProjectMonth; })); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, null, createdProjectsMonth); + return res.send(createdProjectsMonth); }; @@ -68,7 +71,9 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp const projMonthCollection = req.db.collection(CollectionNames.PROJECTS_MONTH); const {value: originalProjectMonth} = await projMonthCollection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: projectMonth}, {returnOriginal: true}); await saveAudit(req, 'projectMonth', originalProjectMonth, projectMonth); - return res.send({_id, ...projectMonth}); + const projectMonthResponse = {_id, ...projectMonth}; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, projectMonthResponse._id, projectMonthResponse); + return res.send(projectMonthResponse); } const inserted = await req.db.collection(CollectionNames.PROJECTS_MONTH).insertOne({ @@ -76,7 +81,7 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp audit: createAudit(req.user), }); const [createdProjectMonth] = inserted.ops; - + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, createdProjectMonth._id, createdProjectMonth); return res.send(createdProjectMonth); }; @@ -86,5 +91,6 @@ export const deleteProjectsMonthController = async (req: ConfacRequest, res: Res const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); await req.db.collection(CollectionNames.ATTACHMENTS_PROJECT_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS_MONTH, id, null); return res.send(id); }; diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts index 27f06b25..52638ceb 100644 --- a/backend/src/controllers/utils/entity-events.ts +++ b/backend/src/controllers/utils/entity-events.ts @@ -2,7 +2,7 @@ import { ObjectID } from 'bson'; import {CollectionNames, SocketEventTypes} from '../../models/common'; import {ConfacRequest} from '../../models/technical'; -export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entityId: ObjectID, entity:any) { +export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entityId: ObjectID | null, entity: any|null) { const sourceSocketId = req.headers['x-socket-id']; const sourceUserEmail = req.user?.data?.email; req.io.emit(eventType, { @@ -17,7 +17,7 @@ export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, interface EntityEventPayload{ entityType: string; entity: any; - entityId: ObjectID; + entityId: ObjectID | null; sourceSocketId: string | undefined; sourceUserEmail: string | undefined; } \ No newline at end of file diff --git a/backend/src/server.ts b/backend/src/server.ts index 91949459..c8f390dd 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -19,7 +19,7 @@ const server = http.createServer(app); const io = new Server(server, { cors: { origin: '*', // Allow all origins - methods: ['GET', 'POST'], // Allowed HTTP methods + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Allowed HTTP methods allowedHeaders: ['x-socket-id'], // Optional: specify allowed headers credentials: true, // Allow credentials (e.g., cookies) }, @@ -31,7 +31,7 @@ sgMail.setApiKey(appConfig.SENDGRID_API_KEY); // Allow only specific origins (e.g., your frontend's URL) const corsOptions = { origin: 'http://localhost:3000', // Replace with your frontend URL - methods: ['GET', 'POST', 'PUT', 'DELETE'], // Allowed HTTP methods + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Allowed HTTP methods allowedHeaders: ['Content-Type', 'Authorization', 'x-socket-id'], // Allowed headers credentials: true, // Allow cookies and credentials }; diff --git a/frontend/src/actions/configActions.ts b/frontend/src/actions/configActions.ts index 7e38edf4..76fde829 100644 --- a/frontend/src/actions/configActions.ts +++ b/frontend/src/actions/configActions.ts @@ -28,7 +28,7 @@ export function updateConfig(newConfig: ConfigModel) { }; } -export function handleconfigSocketEvents(eventType: string, eventPayload: EntityEventPayload){ +export function handleConfigSocketEvents(eventType: string, eventPayload: EntityEventPayload){ return (dispatch: Dispatch) => { dispatch(busyToggle()); switch(eventType){ diff --git a/frontend/src/actions/projectActions.ts b/frontend/src/actions/projectActions.ts index 9cce4171..3f1d04cc 100644 --- a/frontend/src/actions/projectActions.ts +++ b/frontend/src/actions/projectActions.ts @@ -263,4 +263,33 @@ export function handleProjectSocketEvents(eventType: string, eventPayload: Entit } dispatch(busyToggle.off()); } +} + +export function handleProjectMonthSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + if(Array.isArray(eventPayload.entity)){ + dispatch({ + type: ACTION_TYPES.PROJECTS_MONTH_FETCHED, + projectsMonth: eventPayload.entity, + }); + }else{ + dispatch({ + type: ACTION_TYPES.PROJECTS_MONTH_UPDATE, + projectMonth: eventPayload.entity, + }); + } + break; + case SocketEventTypes.EntityDeleted: + dispatch({ + type: ACTION_TYPES.PROJECTS_MONTH_DELETE, + id: eventPayload.entityId, + }); break; + default: throw new Error(`${eventType} not supported for project month.`); + } + dispatch(busyToggle.off()); +} } \ No newline at end of file diff --git a/frontend/src/components/hooks/useEntityChangedToast.tsx b/frontend/src/components/hooks/useEntityChangedToast.tsx index 59269bf1..13608c7e 100644 --- a/frontend/src/components/hooks/useEntityChangedToast.tsx +++ b/frontend/src/components/hooks/useEntityChangedToast.tsx @@ -1,17 +1,17 @@ import { useEffect } from "react"; import { socketService } from "../socketio/SocketService"; -function useEntityChangedToast(entityId: string|null|undefined) { +function useEntityChangedToast(entityId: string|null|undefined, entityType: string|null|undefined = null) { useEffect(()=>{ var subs: undefined| (()=>void); - if(entityId){ - subs = socketService.enableToastsForEntity(entityId); + if(entityId || entityType ){ + subs = socketService.enableToastsForEntity(entityId, entityType); } return subs; - }, [entityId]); + }, [entityId, entityType]); } export default useEntityChangedToast; \ No newline at end of file diff --git a/frontend/src/components/project/EditProjectMonths.tsx b/frontend/src/components/project/EditProjectMonths.tsx index 73882226..ce9b539f 100644 --- a/frontend/src/components/project/EditProjectMonths.tsx +++ b/frontend/src/components/project/EditProjectMonths.tsx @@ -17,6 +17,7 @@ import {useProjectsMonth} from '../hooks/useProjects'; import {EnhanceWithConfirmation} from '../enhancers/EnhanceWithConfirmation'; import {Button} from '../controls/form-controls/Button'; import {useParams} from 'react-router-dom'; +import useEntityChangedToast from '../hooks/useEntityChangedToast'; const ConfirmationButton = EnhanceWithConfirmation(Button); @@ -27,6 +28,8 @@ export const EditProjectMonths = () => { const model = useProjectsMonth(params.projectMonthId); const [projectMonth, setProjectMonth] = useState((model && model.details) || getNewProjectMonth()); + useEntityChangedToast(model?._id); + const docTitle = projectMonth._id ? 'projectMonthEdit' : 'projectMonthNew'; const consultantName = (model && model.consultantName) || ''; const clientName = (model && model.client.name) || ''; diff --git a/frontend/src/components/project/project-month-list/ProjectMonthsLists.tsx b/frontend/src/components/project/project-month-list/ProjectMonthsLists.tsx index 53af9933..a85857ed 100644 --- a/frontend/src/components/project/project-month-list/ProjectMonthsLists.tsx +++ b/frontend/src/components/project/project-month-list/ProjectMonthsLists.tsx @@ -9,6 +9,7 @@ import { ProjectMonthsListToolbar } from './ProjectMonthsListToolbar'; import './project-month-list.scss'; +import useEntityChangedToast from '../../hooks/useEntityChangedToast'; /** The monthly invoicing tables including the top searchbar */ @@ -21,6 +22,8 @@ export const ProjectMonthsLists = () => { .filter((month, index, arr) => arr.indexOf(month) === index) .sort((a, b) => b.localeCompare(a)); + useEntityChangedToast(null, 'projects_month'); + return ( diff --git a/frontend/src/components/socketio/EntityEventPayload.ts b/frontend/src/components/socketio/EntityEventPayload.ts index f3f68843..25c7846d 100644 --- a/frontend/src/components/socketio/EntityEventPayload.ts +++ b/frontend/src/components/socketio/EntityEventPayload.ts @@ -1,7 +1,7 @@ export interface EntityEventPayload { entityType: string; entity: any; - entityId: string; + entityId: string | null; sourceSocketId: string | undefined; sourceUserEmail: string | undefined; } diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index c7d9576c..f61db5aa 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,7 +1,7 @@ import { Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleClientSocketEvents, handleconfigSocketEvents, handleConsultantSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { handleClientSocketEvents, handleConfigSocketEvents, handleConsultantSocketEvents, handleProjectMonthSocketEvents, handleProjectSocketEvents } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; @@ -44,7 +44,8 @@ function createSocketService () { case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; case 'roles': dispatch(handleRoleSocketEvents(eventType, eventPayload)); break; - case 'config': dispatch(handleconfigSocketEvents(eventType, eventPayload)); break; + case 'config': dispatch(handleConfigSocketEvents(eventType, eventPayload)); break; + case 'projects_month': dispatch(handleProjectMonthSocketEvents(eventType, eventPayload)); break; default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); @@ -70,6 +71,7 @@ function createSocketService () { default: throw new Error(`${eventType} not supported.`); } + // TODO nicolas debounce toasts toast.info( t(`socketio.operation.${operation}`, { entityType: t(`socketio.entities.${eventPayload.entityType}`), @@ -79,7 +81,7 @@ function createSocketService () { ); } - function enableToastsForEntity(entityId: string) { + function enableToastsForEntity(entityId: string|null|undefined, entityType: string|null|undefined) { var unsubscriptions: (()=>void)[] = []; function registerHandlerForEventType(eventType: SocketEventTypes){ @@ -89,10 +91,14 @@ function createSocketService () { console.log("Event ignored for entityId subscription => source socket id is self"); return; } - if(msg.entityId !== entityId){ + if(!!entityId && msg.entityId !== entityId){ console.log("Event ignored for entityId subscription => entity id not match"); return; } + if(!!entityType && msg.entityType !== entityType){ + console.log("Event ignored for entityType subscription => entity type not match"); + return; + } toastEntityChanged(eventType, msg); }; diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index fe033c9c..61f93d4c 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -684,7 +684,8 @@ export const trans = { clients: 'Klant', users: 'Gebruiker', roles: 'Rol', - config: 'Configuratie' + config: 'Configuratie', + projects_month: 'Project maand' }, operation: { entityUpdated: '{entityType} werd aangepast door {user}', From 9c5f22bce81b2a22411066713bff7648294c74c2 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 11:38:40 +0100 Subject: [PATCH 11/27] invoice basic working --- backend/src/controllers/clients.ts | 4 +- backend/src/controllers/config.ts | 4 +- backend/src/controllers/consultants.ts | 4 +- backend/src/controllers/invoices.ts | 37 +++++++++++++------ backend/src/controllers/projects.ts | 6 +-- backend/src/controllers/projectsMonth.ts | 8 ++-- backend/src/controllers/user.ts | 8 ++-- .../src/controllers/utils/entity-events.ts | 2 +- frontend/src/actions/invoiceActions.ts | 23 ++++++++++++ frontend/src/actions/projectActions.ts | 2 +- .../invoice/invoice-edit/EditInvoice.tsx | 2 + .../src/components/socketio/SocketService.ts | 19 +++++----- frontend/src/trans.en.ts | 17 +++++++++ frontend/src/trans.nl.ts | 15 ++++---- 14 files changed, 105 insertions(+), 46 deletions(-) diff --git a/backend/src/controllers/clients.ts b/backend/src/controllers/clients.ts index 41f4cbc4..b3f32611 100644 --- a/backend/src/controllers/clients.ts +++ b/backend/src/controllers/clients.ts @@ -33,7 +33,7 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'client', originalClient, client); const clientResponse = {_id, ...client}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CLIENTS, _id, clientResponse); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CLIENTS, entityId: _id, entity: clientResponse }); return res.send(clientResponse); } @@ -42,6 +42,6 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { client.audit = createAudit(req.user); const inserted = await req.db.collection(CollectionNames.CLIENTS).insertOne(client); const [createdClient] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CLIENTS, createdClient._id, createdClient); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CLIENTS, entityId: createdClient._id, entity: createdClient }); return res.send(createdClient); }; diff --git a/backend/src/controllers/config.ts b/backend/src/controllers/config.ts index 5368712c..7aad716d 100644 --- a/backend/src/controllers/config.ts +++ b/backend/src/controllers/config.ts @@ -43,13 +43,13 @@ export const saveCompanyConfig = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'config', originalConfig, config); const responseConfig = {_id, ...config}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONFIG, _id, responseConfig); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CONFIG, entityId: _id, entity: responseConfig }); return res.send(responseConfig); } const inserted = await req.db.collection(CollectionNames.CONFIG).insertOne(config); const responseConfig = inserted.ops[0]; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONFIG, responseConfig._id, responseConfig); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CONFIG, entityId: responseConfig._id, entity: responseConfig }); return res.send(responseConfig); }; diff --git a/backend/src/controllers/consultants.ts b/backend/src/controllers/consultants.ts index 0b45f2f1..faae96b9 100644 --- a/backend/src/controllers/consultants.ts +++ b/backend/src/controllers/consultants.ts @@ -22,7 +22,7 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'consultant', originalConsultant, consultant); const responseConsultant = {_id, ...consultant}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, _id, responseConsultant); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CONSULTANTS, entityId: _id, entity: responseConsultant }); return res.send(responseConsultant); } @@ -34,6 +34,6 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdConsultant] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONSULTANTS, createdConsultant._id, createdConsultant); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CONSULTANTS, entityId: createdConsultant._id, entity: createdConsultant }); return res.send(createdConsultant); }; diff --git a/backend/src/controllers/invoices.ts b/backend/src/controllers/invoices.ts index e723f9f6..5b9a5b28 100644 --- a/backend/src/controllers/invoices.ts +++ b/backend/src/controllers/invoices.ts @@ -4,10 +4,11 @@ import {ObjectID, Db} from 'mongodb'; import {IInvoice, INVOICE_EXCEL_HEADERS} from '../models/invoices'; import {IAttachmentCollection} from '../models/attachments'; import {createPdf, createXml} from './utils'; -import {CollectionNames, IAttachment, createAudit, updateAudit} from '../models/common'; +import {CollectionNames, IAttachment, SocketEventTypes, createAudit, updateAudit} from '../models/common'; import {IProjectMonth} from '../models/projectsMonth'; import {ConfacRequest, Jwt} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; +import {emitEntityEvent} from './utils/entity-events'; const createInvoice = async (invoice: IInvoice, db: Db, pdfBuffer: Buffer, user: Jwt) => { @@ -49,14 +50,16 @@ const moveProjectMonthAttachmentsToInvoice = async (invoice: IInvoice, projectMo const projectMonth = await db.collection(CollectionNames.PROJECTS_MONTH).findOne({_id: projectMonthId}); const updatedAttachmentDetails = projectMonth ? [...invoice.attachments, ...projectMonth?.attachments] : invoice.attachments; - const inserted = await db.collection(CollectionNames.INVOICES) + const updateInvoiceResult = await db.collection(CollectionNames.INVOICES) .findOneAndUpdate({_id: new ObjectID(invoice._id)}, {$set: {attachments: updatedAttachmentDetails}}, {returnOriginal: false}); - const updatedInvoice = inserted.value; + const updatedInvoice = updateInvoiceResult.value; + + const updateProjectMonthResult = await db.collection(CollectionNames.PROJECTS_MONTH).findOneAndUpdate({_id: projectMonthId}, {$set: {attachments: []}}); + const updatedProjectMonth = updateProjectMonthResult.value; - await db.collection(CollectionNames.PROJECTS_MONTH).findOneAndUpdate({_id: projectMonthId}, {$set: {attachments: []}}); await db.collection(CollectionNames.ATTACHMENTS_PROJECT_MONTH).findOneAndDelete({_id: projectMonthId}); - return updatedInvoice; + return {updatedInvoice, updatedProjectMonth}; }; @@ -114,11 +117,13 @@ export const createInvoiceController = async (req: ConfacRequest, res: Response) if (invoice.projectMonth) { const projectMonthId = new ObjectID(invoice.projectMonth.projectMonthId); - const updatedInvoice = await moveProjectMonthAttachmentsToInvoice(createdInvoice, projectMonthId, req.db); - + const {updatedInvoice, updatedProjectMonth} = await moveProjectMonthAttachmentsToInvoice(createdInvoice, projectMonthId, req.db); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.INVOICES, updatedInvoice!._id, updatedInvoice); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth!._id, updatedProjectMonth); return res.send(updatedInvoice); } + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.INVOICES, createdInvoice._id, createdInvoice); return res.send(createdInvoice); }; @@ -172,15 +177,20 @@ export const updateInvoiceController = async (req: ConfacRequest, res: Response) .findOneAndUpdate({_id: new ObjectID(invoice.projectMonth.projectMonthId)}, {$set: {verified: invoice.verified}}); } + const invoiceResponse = {_id, ...invoice}; const result: Array = [{ type: 'invoice', - model: {_id, ...invoice}, + model: invoiceResponse, }]; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.INVOICES, invoiceResponse._id, invoiceResponse); + if (projectMonth && projectMonth.ok && projectMonth.value) { + const projectMonthResponse = projectMonth.value; result.push({ type: 'projectMonth', - model: projectMonth.value, + model: projectMonthResponse, }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, projectMonthResponse._id, projectMonthResponse); } return res.send(result); @@ -189,7 +199,7 @@ export const updateInvoiceController = async (req: ConfacRequest, res: Response) /** Hard invoice delete: There is no coming back from this one */ -export const deleteInvoiceController = async (req: Request, res: Response) => { +export const deleteInvoiceController = async (req: ConfacRequest, res: Response) => { const { id: invoiceId }: { id: string; } = req.body; const invoice = await req.db.collection(CollectionNames.INVOICES).findOne({ _id: new ObjectID(invoiceId) }); @@ -213,12 +223,17 @@ export const deleteInvoiceController = async (req: Request, res: Response) => { const projectMonthCollection = req.db.collection(CollectionNames.PROJECTS_MONTH); const attachments = invoice.attachments.filter(a => a.type !== 'pdf'); - await projectMonthCollection.findOneAndUpdate({ _id: new ObjectID(invoice.projectMonth.projectMonthId) }, { $set: { attachments } }); + const projectMonthId = new ObjectID(invoice.projectMonth.projectMonthId); + const updateProjectMonthResult = await projectMonthCollection.findOneAndUpdate({ _id: projectMonthId }, { $set: { attachments } }); + const updatedProjectMonth = updateProjectMonthResult.value; + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth!._id, updatedProjectMonth); } await req.db.collection(CollectionNames.INVOICES).findOneAndDelete({ _id: new ObjectID(invoiceId) }); await req.db.collection(CollectionNames.ATTACHMENTS).findOneAndDelete({ _id: new ObjectID(invoiceId) }); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.INVOICES, new ObjectID(invoiceId), null); + return res.send(invoiceId); }; diff --git a/backend/src/controllers/projects.ts b/backend/src/controllers/projects.ts index e2c1696e..802e4e97 100644 --- a/backend/src/controllers/projects.ts +++ b/backend/src/controllers/projects.ts @@ -46,7 +46,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { const {value: originalProject} = await projectsColl.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: project}, {returnOriginal: true}); await saveAudit(req, 'project', originalProject, project); const responseProject = {_id, ...project}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS, _id, responseProject); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.PROJECTS, entityId: _id, entity: responseProject }); return res.send(responseProject); } @@ -55,7 +55,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdProject] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS, createdProject._id, createdProject); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS, entityId: createdProject._id, entity: createdProject }); return res.send(createdProject); }; @@ -63,6 +63,6 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { export const deleteProject = async (req: ConfacRequest, res: Response) => { const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS).findOneAndDelete({_id: new ObjectID(id)}); - emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS, id, null); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityDeleted, entityType: CollectionNames.PROJECTS, entityId: id, entity: null }); return res.send(id); }; diff --git a/backend/src/controllers/projectsMonth.ts b/backend/src/controllers/projectsMonth.ts index 92a6ff5b..8054bdf6 100644 --- a/backend/src/controllers/projectsMonth.ts +++ b/backend/src/controllers/projectsMonth.ts @@ -56,7 +56,7 @@ export const createProjectsMonthController = async (req: ConfacRequest, res: Res return createdProjectMonth; })); - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, null, createdProjectsMonth); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS_MONTH, entityId: null, entity: createdProjectsMonth }); return res.send(createdProjectsMonth); }; @@ -72,7 +72,7 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp const {value: originalProjectMonth} = await projMonthCollection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: projectMonth}, {returnOriginal: true}); await saveAudit(req, 'projectMonth', originalProjectMonth, projectMonth); const projectMonthResponse = {_id, ...projectMonth}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, projectMonthResponse._id, projectMonthResponse); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.PROJECTS_MONTH, entityId: projectMonthResponse._id, entity: projectMonthResponse }); return res.send(projectMonthResponse); } @@ -81,7 +81,7 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp audit: createAudit(req.user), }); const [createdProjectMonth] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, createdProjectMonth._id, createdProjectMonth); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS_MONTH, entityId: createdProjectMonth._id, entity: createdProjectMonth }); return res.send(createdProjectMonth); }; @@ -91,6 +91,6 @@ export const deleteProjectsMonthController = async (req: ConfacRequest, res: Res const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); await req.db.collection(CollectionNames.ATTACHMENTS_PROJECT_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); - emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS_MONTH, id, null); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityDeleted, entityType: CollectionNames.PROJECTS_MONTH, entityId: id, entity: null }); return res.send(id); }; diff --git a/backend/src/controllers/user.ts b/backend/src/controllers/user.ts index e8f3a1cd..bbcbcf49 100644 --- a/backend/src/controllers/user.ts +++ b/backend/src/controllers/user.ts @@ -113,14 +113,14 @@ export const saveUser = async (req: ConfacRequest, res: Response) => { const {value: originalUser} = await collection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: user}, {returnOriginal: true}); await saveAudit(req, 'user', originalUser, user); const responseUser = {_id, ...user}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.USERS, _id, responseUser); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.USERS, entityId: _id, entity: responseUser }); return res.send(responseUser); } user.audit = createAudit(req.user); const inserted = await collection.insertOne(user); const [createdUser] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.USERS, createdUser._id, createdUser); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.USERS, entityId: createdUser._id, entity: createdUser }); return res.send(createdUser); }; @@ -148,13 +148,13 @@ export const saveRole = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'role', originalRole, role); const responseRole = {_id, ...role}; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.ROLES, _id, responseRole); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.ROLES, entityId: _id, entity: responseRole }); return res.send(responseRole); } role.audit = createAudit(req.user); const inserted = await collection.insertOne(role); const [createdRole] = inserted.ops; - emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.ROLES, createdRole._id, createdRole); + emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.ROLES, entityId: createdRole._id, entity: createdRole }); return res.send(createdRole); }; diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts index 52638ceb..22d3049b 100644 --- a/backend/src/controllers/utils/entity-events.ts +++ b/backend/src/controllers/utils/entity-events.ts @@ -1,4 +1,4 @@ -import { ObjectID } from 'bson'; +import {ObjectID} from 'bson'; import {CollectionNames, SocketEventTypes} from '../../models/common'; import {ConfacRequest} from '../../models/technical'; diff --git a/frontend/src/actions/invoiceActions.ts b/frontend/src/actions/invoiceActions.ts index 5e993db4..7a0dabd9 100644 --- a/frontend/src/actions/invoiceActions.ts +++ b/frontend/src/actions/invoiceActions.ts @@ -8,6 +8,9 @@ import InvoiceModel from '../components/invoice/models/InvoiceModel'; import {ProjectMonthModel} from '../components/project/models/ProjectMonthModel'; import {authService} from '../components/users/authService'; import { socketService } from '../components/socketio/SocketService'; +import { EntityEventPayload } from '../components/socketio/EntityEventPayload'; +import { SocketEventTypes } from '../components/socketio/SocketEventTypes'; +import { Dispatch } from 'redux'; function cleanViewModel(data: InvoiceModel): InvoiceModel { @@ -119,3 +122,23 @@ export function deleteInvoice(invoice: InvoiceModel) { .then(() => dispatch(busyToggle.off())); }; } + +export function handleInvoiceSocketEvents(eventType: string, eventPayload: EntityEventPayload){ + return (dispatch: Dispatch) => { + dispatch(busyToggle()); + switch(eventType){ + case SocketEventTypes.EntityUpdated: + case SocketEventTypes.EntityCreated: + dispatch({ + type: ACTION_TYPES.INVOICE_UPDATED, + invoice: eventPayload.entity}); break; + case SocketEventTypes.EntityDeleted: + dispatch({ + type: ACTION_TYPES.INVOICE_DELETED, + id: eventPayload.entityId, + }); break; + default: throw new Error(`${eventType} not supported for project month.`); + } + dispatch(busyToggle.off()); + } +} \ No newline at end of file diff --git a/frontend/src/actions/projectActions.ts b/frontend/src/actions/projectActions.ts index 3f1d04cc..d78f4873 100644 --- a/frontend/src/actions/projectActions.ts +++ b/frontend/src/actions/projectActions.ts @@ -291,5 +291,5 @@ export function handleProjectMonthSocketEvents(eventType: string, eventPayload: default: throw new Error(`${eventType} not supported for project month.`); } dispatch(busyToggle.off()); -} + } } \ No newline at end of file diff --git a/frontend/src/components/invoice/invoice-edit/EditInvoice.tsx b/frontend/src/components/invoice/invoice-edit/EditInvoice.tsx index e0fdc41d..5edca64b 100644 --- a/frontend/src/components/invoice/invoice-edit/EditInvoice.tsx +++ b/frontend/src/components/invoice/invoice-edit/EditInvoice.tsx @@ -28,6 +28,7 @@ import {InvoiceDownloadIcon} from '../../controls/attachments/AttachmentDownload import './EditInvoice.scss'; +import useEntityChangedToast from '../../hooks/useEntityChangedToast'; const EditInvoice = () => { @@ -45,6 +46,7 @@ const EditInvoice = () => { const initInvoice = storeInvoice ? new InvoiceModel(config, storeInvoice) : getNewInvoice(config, invoices, clients, {isQuotation}); const fullProjectMonth = useProjectsMonth(storeInvoice?.projectMonth?.projectMonthId); const [invoice, setInvoice] = useState(initInvoice); + useEntityChangedToast(invoice._id); const [, forceUpdate] = useReducer(x => x + 1, 0); const dispatch = useDispatch(); // useEffect(() => window.scrollTo(0, 0)); // TODO: each keystroke made it scroll to top :( diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index f61db5aa..613bdcdd 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,7 +1,7 @@ import { Dispatch } from "redux"; import { io } from "socket.io-client"; -import { handleClientSocketEvents, handleConfigSocketEvents, handleConsultantSocketEvents, handleProjectMonthSocketEvents, handleProjectSocketEvents } from "../../actions"; +import { handleClientSocketEvents, handleConfigSocketEvents, handleConsultantSocketEvents, handleInvoiceSocketEvents, handleProjectMonthSocketEvents, handleProjectSocketEvents } from "../../actions"; import { SocketEventTypes } from "./SocketEventTypes"; import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; @@ -39,14 +39,15 @@ function createSocketService () { } switch(eventPayload.entityType){ - case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; - case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; - case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; - case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; - case 'roles': dispatch(handleRoleSocketEvents(eventType, eventPayload)); break; - case 'config': dispatch(handleConfigSocketEvents(eventType, eventPayload)); break; - case 'projects_month': dispatch(handleProjectMonthSocketEvents(eventType, eventPayload)); break; - default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); + case 'clients': dispatch(handleClientSocketEvents(eventType, eventPayload)); break; + case 'config': dispatch(handleConfigSocketEvents(eventType, eventPayload)); break; + case 'consultants': dispatch(handleConsultantSocketEvents(eventType, eventPayload)); break; + case 'invoices': dispatch(handleInvoiceSocketEvents(eventType, eventPayload)); break; + case 'projects': dispatch(handleProjectSocketEvents(eventType, eventPayload)); break; + case 'projects_month': dispatch(handleProjectMonthSocketEvents(eventType, eventPayload)); break; + case 'roles': dispatch(handleRoleSocketEvents(eventType, eventPayload)); break; + case 'users': dispatch(handleUserSocketEvents(eventType, eventPayload)); break; + default: throw new Error(`${eventPayload.entityType} event for entity type not supported.`); }; }); } diff --git a/frontend/src/trans.en.ts b/frontend/src/trans.en.ts index b093551f..b6fbf593 100644 --- a/frontend/src/trans.en.ts +++ b/frontend/src/trans.en.ts @@ -676,5 +676,22 @@ export const trans = { } } } + }, + socketio: { + entities: { + clients: 'Client', + config: 'Configuration', + consultants: 'Consultant', + invoices: 'Invoice', + projects: 'Project', + projects_month: 'Project month', + roles: 'Role', + users: 'User', + }, + operation: { + entityCreated: '{entityType} has been created by {user}', + entityUpdated: '{entityType} has been updated by {user}', + entityDeleted: '{entityType} has been deleted by {user}' + } } }; diff --git a/frontend/src/trans.nl.ts b/frontend/src/trans.nl.ts index 61f93d4c..cc3d887e 100644 --- a/frontend/src/trans.nl.ts +++ b/frontend/src/trans.nl.ts @@ -679,18 +679,19 @@ export const trans = { }, socketio: { entities: { - projects: 'Project', - consultants: 'Consultant', clients: 'Klant', - users: 'Gebruiker', - roles: 'Rol', config: 'Configuratie', - projects_month: 'Project maand' + consultants: 'Consultant', + invoices: 'Factuur', + projects: 'Project', + projects_month: 'Project maand', + roles: 'Rol', + users: 'Gebruiker', }, operation: { - entityUpdated: '{entityType} werd aangepast door {user}', entityCreated: '{entityType} werd aangemaakt door {user}', + entityUpdated: '{entityType} werd aangepast door {user}', entityDeleted: '{entityType} werd verwijderd door {user}' } - } // TODO nicolas vertaling overnemen in .en file + } }; From 2d8c914e8a14149ad4f3e6d8f394e16afe8a8f08 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 12:14:16 +0100 Subject: [PATCH 12/27] emit event correctie --- backend/src/controllers/clients.ts | 4 ++-- backend/src/controllers/config.ts | 4 ++-- backend/src/controllers/consultants.ts | 4 ++-- backend/src/controllers/invoices.ts | 9 +++++++-- backend/src/controllers/projects.ts | 6 +++--- backend/src/controllers/projectsMonth.ts | 8 ++++---- backend/src/controllers/user.ts | 8 ++++---- 7 files changed, 24 insertions(+), 19 deletions(-) diff --git a/backend/src/controllers/clients.ts b/backend/src/controllers/clients.ts index b3f32611..41f4cbc4 100644 --- a/backend/src/controllers/clients.ts +++ b/backend/src/controllers/clients.ts @@ -33,7 +33,7 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'client', originalClient, client); const clientResponse = {_id, ...client}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CLIENTS, entityId: _id, entity: clientResponse }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CLIENTS, _id, clientResponse); return res.send(clientResponse); } @@ -42,6 +42,6 @@ export const saveClient = async (req: ConfacRequest, res: Response) => { client.audit = createAudit(req.user); const inserted = await req.db.collection(CollectionNames.CLIENTS).insertOne(client); const [createdClient] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CLIENTS, entityId: createdClient._id, entity: createdClient }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CLIENTS, createdClient._id, createdClient); return res.send(createdClient); }; diff --git a/backend/src/controllers/config.ts b/backend/src/controllers/config.ts index 7aad716d..5368712c 100644 --- a/backend/src/controllers/config.ts +++ b/backend/src/controllers/config.ts @@ -43,13 +43,13 @@ export const saveCompanyConfig = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'config', originalConfig, config); const responseConfig = {_id, ...config}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CONFIG, entityId: _id, entity: responseConfig }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONFIG, _id, responseConfig); return res.send(responseConfig); } const inserted = await req.db.collection(CollectionNames.CONFIG).insertOne(config); const responseConfig = inserted.ops[0]; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CONFIG, entityId: responseConfig._id, entity: responseConfig }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONFIG, responseConfig._id, responseConfig); return res.send(responseConfig); }; diff --git a/backend/src/controllers/consultants.ts b/backend/src/controllers/consultants.ts index faae96b9..4136fc74 100644 --- a/backend/src/controllers/consultants.ts +++ b/backend/src/controllers/consultants.ts @@ -22,7 +22,7 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'consultant', originalConsultant, consultant); const responseConsultant = {_id, ...consultant}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.CONSULTANTS, entityId: _id, entity: responseConsultant }); + emitEntityEvent(SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, _id, responseConsultant); return res.send(responseConsultant); } @@ -34,6 +34,6 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdConsultant] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.CONSULTANTS, entityId: createdConsultant._id, entity: createdConsultant }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.CONSULTANTS, createdConsultant._id, createdConsultant); return res.send(createdConsultant); }; diff --git a/backend/src/controllers/invoices.ts b/backend/src/controllers/invoices.ts index 5b9a5b28..3f590ff2 100644 --- a/backend/src/controllers/invoices.ts +++ b/backend/src/controllers/invoices.ts @@ -119,7 +119,9 @@ export const createInvoiceController = async (req: ConfacRequest, res: Response) const projectMonthId = new ObjectID(invoice.projectMonth.projectMonthId); const {updatedInvoice, updatedProjectMonth} = await moveProjectMonthAttachmentsToInvoice(createdInvoice, projectMonthId, req.db); emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.INVOICES, updatedInvoice!._id, updatedInvoice); - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth!._id, updatedProjectMonth); + if (updatedProjectMonth) { + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth!._id, updatedProjectMonth); + } return res.send(updatedInvoice); } @@ -223,10 +225,13 @@ export const deleteInvoiceController = async (req: ConfacRequest, res: Response) const projectMonthCollection = req.db.collection(CollectionNames.PROJECTS_MONTH); const attachments = invoice.attachments.filter(a => a.type !== 'pdf'); + const projectMonthId = new ObjectID(invoice.projectMonth.projectMonthId); const updateProjectMonthResult = await projectMonthCollection.findOneAndUpdate({ _id: projectMonthId }, { $set: { attachments } }); const updatedProjectMonth = updateProjectMonthResult.value; - emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth!._id, updatedProjectMonth); + if (updatedProjectMonth) { + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, updatedProjectMonth._id, updatedProjectMonth); + } } await req.db.collection(CollectionNames.INVOICES).findOneAndDelete({ _id: new ObjectID(invoiceId) }); diff --git a/backend/src/controllers/projects.ts b/backend/src/controllers/projects.ts index 802e4e97..e2c1696e 100644 --- a/backend/src/controllers/projects.ts +++ b/backend/src/controllers/projects.ts @@ -46,7 +46,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { const {value: originalProject} = await projectsColl.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: project}, {returnOriginal: true}); await saveAudit(req, 'project', originalProject, project); const responseProject = {_id, ...project}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.PROJECTS, entityId: _id, entity: responseProject }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS, _id, responseProject); return res.send(responseProject); } @@ -55,7 +55,7 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { audit: createAudit(req.user), }); const [createdProject] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS, entityId: createdProject._id, entity: createdProject }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS, createdProject._id, createdProject); return res.send(createdProject); }; @@ -63,6 +63,6 @@ export const saveProject = async (req: ConfacRequest, res: Response) => { export const deleteProject = async (req: ConfacRequest, res: Response) => { const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS).findOneAndDelete({_id: new ObjectID(id)}); - emitEntityEvent({ req, eventType: SocketEventTypes.EntityDeleted, entityType: CollectionNames.PROJECTS, entityId: id, entity: null }); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS, id, null); return res.send(id); }; diff --git a/backend/src/controllers/projectsMonth.ts b/backend/src/controllers/projectsMonth.ts index 8054bdf6..81314639 100644 --- a/backend/src/controllers/projectsMonth.ts +++ b/backend/src/controllers/projectsMonth.ts @@ -56,7 +56,7 @@ export const createProjectsMonthController = async (req: ConfacRequest, res: Res return createdProjectMonth; })); - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS_MONTH, entityId: null, entity: createdProjectsMonth }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, null, createdProjectsMonth ); return res.send(createdProjectsMonth); }; @@ -72,7 +72,7 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp const {value: originalProjectMonth} = await projMonthCollection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: projectMonth}, {returnOriginal: true}); await saveAudit(req, 'projectMonth', originalProjectMonth, projectMonth); const projectMonthResponse = {_id, ...projectMonth}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.PROJECTS_MONTH, entityId: projectMonthResponse._id, entity: projectMonthResponse }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.PROJECTS_MONTH, projectMonthResponse._id, projectMonthResponse); return res.send(projectMonthResponse); } @@ -81,7 +81,7 @@ export const patchProjectsMonthController = async (req: ConfacRequest, res: Resp audit: createAudit(req.user), }); const [createdProjectMonth] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.PROJECTS_MONTH, entityId: createdProjectMonth._id, entity: createdProjectMonth }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.PROJECTS_MONTH, createdProjectMonth._id, createdProjectMonth); return res.send(createdProjectMonth); }; @@ -91,6 +91,6 @@ export const deleteProjectsMonthController = async (req: ConfacRequest, res: Res const id = req.body.id; await req.db.collection(CollectionNames.PROJECTS_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); await req.db.collection(CollectionNames.ATTACHMENTS_PROJECT_MONTH).findOneAndDelete({ _id: new ObjectID(id) }); - emitEntityEvent({ req, eventType: SocketEventTypes.EntityDeleted, entityType: CollectionNames.PROJECTS_MONTH, entityId: id, entity: null }); + emitEntityEvent(req, SocketEventTypes.EntityDeleted, CollectionNames.PROJECTS_MONTH, id, null); return res.send(id); }; diff --git a/backend/src/controllers/user.ts b/backend/src/controllers/user.ts index bbcbcf49..e8f3a1cd 100644 --- a/backend/src/controllers/user.ts +++ b/backend/src/controllers/user.ts @@ -113,14 +113,14 @@ export const saveUser = async (req: ConfacRequest, res: Response) => { const {value: originalUser} = await collection.findOneAndUpdate({_id: new ObjectID(_id)}, {$set: user}, {returnOriginal: true}); await saveAudit(req, 'user', originalUser, user); const responseUser = {_id, ...user}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.USERS, entityId: _id, entity: responseUser }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.USERS, _id, responseUser); return res.send(responseUser); } user.audit = createAudit(req.user); const inserted = await collection.insertOne(user); const [createdUser] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.USERS, entityId: createdUser._id, entity: createdUser }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.USERS, createdUser._id, createdUser); return res.send(createdUser); }; @@ -148,13 +148,13 @@ export const saveRole = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'role', originalRole, role); const responseRole = {_id, ...role}; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityUpdated, entityType: CollectionNames.ROLES, entityId: _id, entity: responseRole }); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.ROLES, _id, responseRole); return res.send(responseRole); } role.audit = createAudit(req.user); const inserted = await collection.insertOne(role); const [createdRole] = inserted.ops; - emitEntityEvent({ req, eventType: SocketEventTypes.EntityCreated, entityType: CollectionNames.ROLES, entityId: createdRole._id, entity: createdRole }); + emitEntityEvent(req, SocketEventTypes.EntityCreated, CollectionNames.ROLES, createdRole._id, createdRole); return res.send(createdRole); }; From c6b1684d812fdaf07270883e7271262a141dc243 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 12:21:05 +0100 Subject: [PATCH 13/27] emit event correctie --- backend/src/controllers/consultants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/consultants.ts b/backend/src/controllers/consultants.ts index 4136fc74..7f0af710 100644 --- a/backend/src/controllers/consultants.ts +++ b/backend/src/controllers/consultants.ts @@ -5,7 +5,7 @@ import {IConsultant} from '../models/consultants'; import {CollectionNames, createAudit, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; -import { emitEntityEvent } from './utils/entity-events'; +import {emitEntityEvent} from './utils/entity-events'; export const getConsultants = async (req: Request, res: Response) => { const consultants = await req.db.collection(CollectionNames.CONSULTANTS).find().toArray(); @@ -22,7 +22,7 @@ export const saveConsultant = async (req: ConfacRequest, res: Response) => { await saveAudit(req, 'consultant', originalConsultant, consultant); const responseConsultant = {_id, ...consultant}; - emitEntityEvent(SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, _id, responseConsultant); + emitEntityEvent(req, SocketEventTypes.EntityUpdated, CollectionNames.CONSULTANTS, _id, responseConsultant); return res.send(responseConsultant); } From 8d7524fb5bc584a442c8b1a13ccdac690d2961b0 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 12:29:56 +0100 Subject: [PATCH 14/27] Minor fix --- backend/src/controllers/projectsMonth.ts | 2 +- frontend/src/components/socketio/SocketService.ts | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/src/controllers/projectsMonth.ts b/backend/src/controllers/projectsMonth.ts index 81314639..18755b07 100644 --- a/backend/src/controllers/projectsMonth.ts +++ b/backend/src/controllers/projectsMonth.ts @@ -5,7 +5,7 @@ import {IProjectMonth, IProjectMonthOverview, TimesheetCheckAttachmentType} from import {CollectionNames, createAudit, SocketEventTypes, updateAudit} from '../models/common'; import {ConfacRequest} from '../models/technical'; import {saveAudit} from './utils/audit-logs'; -import { emitEntityEvent } from './utils/entity-events'; +import {emitEntityEvent} from './utils/entity-events'; export const getProjectsPerMonthController = async (req: Request, res: Response) => { diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 613bdcdd..19a6bf9f 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -1,4 +1,4 @@ - + import { Dispatch } from "redux"; import { io } from "socket.io-client"; import { handleClientSocketEvents, handleConfigSocketEvents, handleConsultantSocketEvents, handleInvoiceSocketEvents, handleProjectMonthSocketEvents, handleProjectSocketEvents } from "../../actions"; @@ -28,13 +28,12 @@ function createSocketService () { function registerHandlerForEventType(eventType: SocketEventTypes, dispatch: Dispatch){ socket.on(eventType, eventPayload=> { - console.log("Received entity event from socketio: " + eventType); - console.log("Source socket Id: " + eventPayload.sourceSocketId); - console.log("Payload:"); + console.log("Socket.io: Received entity event" + eventType); + console.log("Socket.io: Payload"); console.log(eventPayload); if(eventPayload.sourceSocketId === socketId){ - console.log("Event ignored: sourceSocketId is equal to current socket id."); + console.log("Socket.io: Event ignored => sourceSocketId is equal to current socket id."); return; } @@ -89,15 +88,15 @@ function createSocketService () { var handleEvent = (msg: EntityEventPayload)=>{ if(msg.sourceSocketId === socketId){ - console.log("Event ignored for entityId subscription => source socket id is self"); + console.log("Socket.io: Event ignored => sourceSocketId is equal to current socket id."); return; } if(!!entityId && msg.entityId !== entityId){ - console.log("Event ignored for entityId subscription => entity id not match"); + console.log("Socket.io: Event ignored for entityId subscription => entity id not match"); return; } if(!!entityType && msg.entityType !== entityType){ - console.log("Event ignored for entityType subscription => entity type not match"); + console.log("Socket.io: Event ignored for entityType subscription => entity type not match"); return; } toastEntityChanged(eventType, msg); From a73abb2e8ff0528e2880dbf92b83583191142211 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 12:57:12 +0100 Subject: [PATCH 15/27] fixtests --- backend/src/routes/invoices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/routes/invoices.ts b/backend/src/routes/invoices.ts index caac442e..0fa3d3b2 100644 --- a/backend/src/routes/invoices.ts +++ b/backend/src/routes/invoices.ts @@ -16,7 +16,7 @@ invoicesRouter.post('/excel', generateExcelForInvoicesController); invoicesRouter.put('/', updateInvoiceController as any); -invoicesRouter.delete('/', deleteInvoiceController); +invoicesRouter.delete('/', deleteInvoiceController as any); invoicesRouter.get('/xml/:id', getInvoiceXmlController); export default invoicesRouter; From 68b62e4937f2ae42bb3a2426c68e68df32fd5ebd Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 13:08:33 +0100 Subject: [PATCH 16/27] fix failing build --- frontend/src/components/consultant/EditConsultant.tsx | 3 +-- frontend/src/components/project/EditProject.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/consultant/EditConsultant.tsx b/frontend/src/components/consultant/EditConsultant.tsx index 357c2acc..1ca15e51 100644 --- a/frontend/src/components/consultant/EditConsultant.tsx +++ b/frontend/src/components/consultant/EditConsultant.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import {useState} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {Container, Row, Form, Alert} from 'react-bootstrap'; import {useNavigate} from 'react-router-dom'; @@ -15,7 +15,6 @@ import {useDocumentTitle} from '../hooks/useDocumentTitle'; import {Audit} from '../admin/audit/Audit'; import {Claim} from '../users/models/UserModel'; import {useParams} from 'react-router-dom'; -import { socketService } from '../socketio/SocketService'; import useEntityChangedToast from '../hooks/useEntityChangedToast'; diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index 7726b41d..d9fb115f 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import {useState} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {Container, Row, Form} from 'react-bootstrap'; import {useNavigate} from 'react-router-dom'; From 0c891630e192f7c980fd666f238cf4cc6efab763 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 13:08:43 +0100 Subject: [PATCH 17/27] fix failing build --- frontend/src/components/project/EditProject.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index d9fb115f..a30c4257 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -24,7 +24,6 @@ import { SingleContractIcon } from "../client/contract/SingleContractIcon"; import {EnhanceWithConfirmation} from '../enhancers/EnhanceWithConfirmation'; import {Button} from '../controls/form-controls/Button'; import {isDateIntervalValid} from '../controls/other/ProjectValidator'; -import { socketService } from '../socketio/SocketService'; import useEntityChangedToast from '../hooks/useEntityChangedToast'; From c28bc7169896882d97f064d015ce183828993656 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:00:44 +0100 Subject: [PATCH 18/27] failing test --- backend/src/controllers/utils/entity-events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/controllers/utils/entity-events.ts b/backend/src/controllers/utils/entity-events.ts index 22d3049b..252d3527 100644 --- a/backend/src/controllers/utils/entity-events.ts +++ b/backend/src/controllers/utils/entity-events.ts @@ -3,7 +3,7 @@ import {CollectionNames, SocketEventTypes} from '../../models/common'; import {ConfacRequest} from '../../models/technical'; export function emitEntityEvent(req: ConfacRequest, eventType: SocketEventTypes, entityType: CollectionNames, entityId: ObjectID | null, entity: any|null) { - const sourceSocketId = req.headers['x-socket-id']; + const sourceSocketId = req.headers ? req.headers['x-socket-id'] : null; const sourceUserEmail = req.user?.data?.email; req.io.emit(eventType, { entityType, From 491956d84bea0179c7b2c6572785591a6c512ceb Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:12:44 +0100 Subject: [PATCH 19/27] cors config --- backend/src/server.ts | 24 +++++++------------ .../src/components/socketio/SocketService.ts | 4 ++-- frontend/src/config-front.js | 8 ++++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index c8f390dd..e1305c3a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -15,27 +15,19 @@ import appRouter from './routes'; const app = express(); const server = http.createServer(app); -// TODO nicolas finetune CORS config... +const corsOptions = { + origins: '*', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], + allowedHeaders: ['Content-Type', 'Authorization', 'x-socket-id'], + credentials: true, +}; + const io = new Server(server, { - cors: { - origin: '*', // Allow all origins - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Allowed HTTP methods - allowedHeaders: ['x-socket-id'], // Optional: specify allowed headers - credentials: true, // Allow credentials (e.g., cookies) - }, + cors: corsOptions }); sgMail.setApiKey(appConfig.SENDGRID_API_KEY); -// TODO nicolas finetune CORS config... -// Allow only specific origins (e.g., your frontend's URL) -const corsOptions = { - origin: 'http://localhost:3000', // Replace with your frontend URL - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], // Allowed HTTP methods - allowedHeaders: ['Content-Type', 'Authorization', 'x-socket-id'], // Allowed headers - credentials: true, // Allow cookies and credentials -}; - app.use(cors(corsOptions)); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); diff --git a/frontend/src/components/socketio/SocketService.ts b/frontend/src/components/socketio/SocketService.ts index 19a6bf9f..d5b5a9b0 100644 --- a/frontend/src/components/socketio/SocketService.ts +++ b/frontend/src/components/socketio/SocketService.ts @@ -7,10 +7,10 @@ import { EntityEventPayload } from "./EntityEventPayload"; import { t } from "../utils"; import { toast } from "react-toastify"; import { handleRoleSocketEvents, handleUserSocketEvents } from "../../actions/userActions"; +import { baseUrl } from "../../config-front"; function createSocketService () { - // TODO nicolas read server url from frontend config !!! - const socket = io('localhost:9000'); + const socket = io(baseUrl); var socketId: undefined|string = undefined; var initialized = false; diff --git a/frontend/src/config-front.js b/frontend/src/config-front.js index 56802166..92b44e00 100644 --- a/frontend/src/config-front.js +++ b/frontend/src/config-front.js @@ -1,7 +1,9 @@ if (process.env.NODE_ENV === 'production') { console.log('process.env', process.env); - const backend = `${window.location.origin}/api`; - module.exports = {backend}; + const baseUrl = window.location.origin; + const backend = `${baseUrl}/api`; + module.exports = {backend, baseUrl}; } else { - module.exports = {backend: 'http://localhost:9000/api'}; + const baseUrl = 'http://localhost:9000'; + module.exports = {backend: baseUrl+'/api',baseUrl}; } From 57a0b8358f85817075349563bef8ec8769f0fdaa Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:14:00 +0100 Subject: [PATCH 20/27] comment --- backend/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index e1305c3a..36d053ef 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -16,7 +16,7 @@ const app = express(); const server = http.createServer(app); const corsOptions = { - origins: '*', + origins: '*', // TODO allow frontend only. methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization', 'x-socket-id'], credentials: true, From e930a935da87dedfa2c855e2b9259730726b83d5 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:25:02 +0100 Subject: [PATCH 21/27] cleaning_wip --- frontend/src/components/consultant/EditConsultant.tsx | 2 +- frontend/src/components/project/EditProject.tsx | 2 +- frontend/src/components/project/EditProjectMonths.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/consultant/EditConsultant.tsx b/frontend/src/components/consultant/EditConsultant.tsx index 1ca15e51..80453591 100644 --- a/frontend/src/components/consultant/EditConsultant.tsx +++ b/frontend/src/components/consultant/EditConsultant.tsx @@ -28,7 +28,7 @@ export const EditConsultant = () => { .filter(x => x.email === consultant.email) .find(x => x.slug !== params.id && x._id !== params.id)); - useEntityChangedToast(model?._id); + useEntityChangedToast(consultant._id); const docTitle = consultant._id ? 'consultantEdit' : 'consultantNew'; useDocumentTitle(docTitle, {name: `${consultant.firstName} ${consultant.name}`}); diff --git a/frontend/src/components/project/EditProject.tsx b/frontend/src/components/project/EditProject.tsx index a30c4257..932d0da3 100644 --- a/frontend/src/components/project/EditProject.tsx +++ b/frontend/src/components/project/EditProject.tsx @@ -43,7 +43,7 @@ export const EditProject = () => { const hasProjectMonths = useSelector((state: ConfacState) => state.projectsMonth.some(pm => pm.projectId === params.id)); const [needsSync, setNeedsSync] = useState<{consultant: boolean, client: boolean}>({consultant: false, client: false}); - useEntityChangedToast(model?._id); + useEntityChangedToast(project._id); const docTitle = consultant._id ? 'projectEdit' : 'projectNew'; useDocumentTitle(docTitle, {consultant: consultant.firstName, client: client.name}); diff --git a/frontend/src/components/project/EditProjectMonths.tsx b/frontend/src/components/project/EditProjectMonths.tsx index ce9b539f..1e2698c1 100644 --- a/frontend/src/components/project/EditProjectMonths.tsx +++ b/frontend/src/components/project/EditProjectMonths.tsx @@ -28,7 +28,7 @@ export const EditProjectMonths = () => { const model = useProjectsMonth(params.projectMonthId); const [projectMonth, setProjectMonth] = useState((model && model.details) || getNewProjectMonth()); - useEntityChangedToast(model?._id); + useEntityChangedToast(projectMonth._id); const docTitle = projectMonth._id ? 'projectMonthEdit' : 'projectMonthNew'; const consultantName = (model && model.consultantName) || ''; From 6e1d9fdebf1bfb07baf0aca88d2a2980b3758421 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:47:14 +0100 Subject: [PATCH 22/27] socketmock --- backend/package-lock.json | 20 +++++++++++++++++++ backend/package.json | 1 + .../src/controllers/tests/1-clients.test.ts | 4 +++- .../controllers/tests/2-consultants.test.ts | 2 ++ backend/src/models/technical.ts | 1 + backend/types/express/index.d.ts | 2 +- 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 10e61684..5b553fb0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -84,6 +84,7 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", + "socket.io-mock": "^1.3.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" @@ -16646,6 +16647,16 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-mock": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", + "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -31827,6 +31838,15 @@ "ws": "~8.17.1" } }, + "socket.io-mock": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", + "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0" + } + }, "socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", diff --git a/backend/package.json b/backend/package.json index 3d3b73ba..c9c7f0cf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -102,6 +102,7 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", + "socket.io-mock": "^1.3.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" diff --git a/backend/src/controllers/tests/1-clients.test.ts b/backend/src/controllers/tests/1-clients.test.ts index 7fccf11d..e7351ab9 100644 --- a/backend/src/controllers/tests/1-clients.test.ts +++ b/backend/src/controllers/tests/1-clients.test.ts @@ -8,7 +8,8 @@ import { IAudit } from '../../models/common'; import { ObjectID } from 'mongodb' import { IClient } from '../../models/clients' import { Jwt } from '../../models/technical' -import { saveClient } from '../clients' +import { saveClient } from '../clients'; +import MockedSocket from 'socket.io-mock'; const fakeUser: Jwt = { data: { @@ -44,6 +45,7 @@ describe('clients controller :: saveClient creation', () => { user: fakeUser, body: {...fakeClient, name: 'Company X'}, db: fakeDb, + io: new MockedSocket(), } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/2-consultants.test.ts b/backend/src/controllers/tests/2-consultants.test.ts index 726cbe68..b3d9c35f 100644 --- a/backend/src/controllers/tests/2-consultants.test.ts +++ b/backend/src/controllers/tests/2-consultants.test.ts @@ -12,6 +12,7 @@ import { saveConsultant } from '../consultants'; import { IConsultant } from '../../models/consultants'; import { MongoMemoryServer } from 'mongodb-memory-server'; + const fakeUser: Jwt = { data: { _id: '_id', email: 'string', firstName: 'first', name: 'name', alias: 'alias', active: true }, iat: 0, exp: 0, @@ -29,6 +30,7 @@ const createFakeRequestAndResponse = (db: Db, consultant: Partial | user: fakeUser, body: {...fakeConsultant, ...(consultant || {})}, db, + io: new MockedSocket(), } as ConfacRequest; const res = { diff --git a/backend/src/models/technical.ts b/backend/src/models/technical.ts index 9c7c1346..28705981 100644 --- a/backend/src/models/technical.ts +++ b/backend/src/models/technical.ts @@ -1,4 +1,5 @@ import {Request} from 'express'; +import { Server } from 'socket.io'; export type Jwt = { data: { diff --git a/backend/types/express/index.d.ts b/backend/types/express/index.d.ts index 1a7a456b..a12eadbd 100644 --- a/backend/types/express/index.d.ts +++ b/backend/types/express/index.d.ts @@ -5,7 +5,7 @@ declare global { namespace Express { export interface Request { db: Db; - io: Server + io: Server; } } } From 5fedbe950941f9c147b181381699dae11d77fb19 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:49:28 +0100 Subject: [PATCH 23/27] socket mock rest of tests --- backend/src/controllers/tests/2-consultants.test.ts | 2 +- backend/src/controllers/tests/3-projectsMonth.test.ts | 4 +++- backend/src/controllers/tests/4-projectsMonth.test.ts | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/tests/2-consultants.test.ts b/backend/src/controllers/tests/2-consultants.test.ts index b3d9c35f..cb662fc7 100644 --- a/backend/src/controllers/tests/2-consultants.test.ts +++ b/backend/src/controllers/tests/2-consultants.test.ts @@ -11,7 +11,7 @@ import { Jwt } from '../../models/technical' import { saveConsultant } from '../consultants'; import { IConsultant } from '../../models/consultants'; import { MongoMemoryServer } from 'mongodb-memory-server'; - +import MockedSocket from 'socket.io-mock'; const fakeUser: Jwt = { data: { _id: '_id', email: 'string', firstName: 'first', name: 'name', alias: 'alias', active: true }, diff --git a/backend/src/controllers/tests/3-projectsMonth.test.ts b/backend/src/controllers/tests/3-projectsMonth.test.ts index ba5f6bd9..8fbb1636 100644 --- a/backend/src/controllers/tests/3-projectsMonth.test.ts +++ b/backend/src/controllers/tests/3-projectsMonth.test.ts @@ -12,6 +12,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; +import MockedSocket from 'socket.io-mock'; @@ -20,7 +21,8 @@ const getFakeDb: jest.Mock = jest.fn(); const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { - req.db = getFakeDb() + req.db = getFakeDb(); + req.io = new MockedSocket(); next(); }); app.use('/', projectsRouter); diff --git a/backend/src/controllers/tests/4-projectsMonth.test.ts b/backend/src/controllers/tests/4-projectsMonth.test.ts index 7c88b493..fc44fc5d 100644 --- a/backend/src/controllers/tests/4-projectsMonth.test.ts +++ b/backend/src/controllers/tests/4-projectsMonth.test.ts @@ -11,6 +11,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; +import MockedSocket from 'socket.io-mock'; @@ -19,7 +20,8 @@ const getFakeDb: jest.Mock = jest.fn(); const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { - req.db = getFakeDb() + req.db = getFakeDb(); + req.io = new MockedSocket(); next(); }); app.use('/', projectsRouter); From 181f970d6d15b91c4649dc2ba927b1233ce418a7 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 14:56:55 +0100 Subject: [PATCH 24/27] fix socketmock --- backend/src/controllers/tests/1-clients.test.ts | 4 ++-- backend/src/controllers/tests/2-consultants.test.ts | 4 ++-- backend/src/controllers/tests/3-projectsMonth.test.ts | 4 ++-- backend/src/controllers/tests/4-projectsMonth.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/controllers/tests/1-clients.test.ts b/backend/src/controllers/tests/1-clients.test.ts index e7351ab9..9c801e16 100644 --- a/backend/src/controllers/tests/1-clients.test.ts +++ b/backend/src/controllers/tests/1-clients.test.ts @@ -9,7 +9,7 @@ import { ObjectID } from 'mongodb' import { IClient } from '../../models/clients' import { Jwt } from '../../models/technical' import { saveClient } from '../clients'; -import MockedSocket from 'socket.io-mock'; +import SocketMock from 'socket.io-mock'; const fakeUser: Jwt = { data: { @@ -45,7 +45,7 @@ describe('clients controller :: saveClient creation', () => { user: fakeUser, body: {...fakeClient, name: 'Company X'}, db: fakeDb, - io: new MockedSocket(), + io: new SocketMock(), } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/2-consultants.test.ts b/backend/src/controllers/tests/2-consultants.test.ts index cb662fc7..6412b069 100644 --- a/backend/src/controllers/tests/2-consultants.test.ts +++ b/backend/src/controllers/tests/2-consultants.test.ts @@ -11,7 +11,7 @@ import { Jwt } from '../../models/technical' import { saveConsultant } from '../consultants'; import { IConsultant } from '../../models/consultants'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import MockedSocket from 'socket.io-mock'; +import SocketMock from 'socket.io-mock'; const fakeUser: Jwt = { data: { _id: '_id', email: 'string', firstName: 'first', name: 'name', alias: 'alias', active: true }, @@ -30,7 +30,7 @@ const createFakeRequestAndResponse = (db: Db, consultant: Partial | user: fakeUser, body: {...fakeConsultant, ...(consultant || {})}, db, - io: new MockedSocket(), + io: new SocketMock(), } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/3-projectsMonth.test.ts b/backend/src/controllers/tests/3-projectsMonth.test.ts index 8fbb1636..9dd50f9a 100644 --- a/backend/src/controllers/tests/3-projectsMonth.test.ts +++ b/backend/src/controllers/tests/3-projectsMonth.test.ts @@ -12,7 +12,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import MockedSocket from 'socket.io-mock'; +import SocketMock from 'socket.io-mock'; @@ -22,7 +22,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new MockedSocket(); + req.io = new SocketMock(); next(); }); app.use('/', projectsRouter); diff --git a/backend/src/controllers/tests/4-projectsMonth.test.ts b/backend/src/controllers/tests/4-projectsMonth.test.ts index fc44fc5d..04751801 100644 --- a/backend/src/controllers/tests/4-projectsMonth.test.ts +++ b/backend/src/controllers/tests/4-projectsMonth.test.ts @@ -11,7 +11,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import MockedSocket from 'socket.io-mock'; +import SocketMock from 'socket.io-mock'; @@ -21,7 +21,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new MockedSocket(); + req.io = new SocketMock(); next(); }); app.use('/', projectsRouter); From 8f7b6565a991fd084e5b12e10786a819755dec97 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 15:04:26 +0100 Subject: [PATCH 25/27] try import --- backend/package-lock.json | 10 +++------- backend/package.json | 2 +- backend/src/controllers/tests/1-clients.test.ts | 4 ++-- backend/src/controllers/tests/2-consultants.test.ts | 4 ++-- backend/src/controllers/tests/3-projectsMonth.test.ts | 4 ++-- backend/src/controllers/tests/4-projectsMonth.test.ts | 4 ++-- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 5b553fb0..17a0ba3c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -35,6 +35,7 @@ "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", "socket.io": "^4.8.1", + "socket.io-mock": "^1.3.2", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" @@ -84,7 +85,6 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", - "socket.io-mock": "^1.3.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" @@ -6320,8 +6320,7 @@ "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -16651,7 +16650,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", - "dev": true, "license": "MIT", "dependencies": { "component-emitter": "^1.3.0" @@ -23835,8 +23833,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", @@ -31842,7 +31839,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", - "dev": true, "requires": { "component-emitter": "^1.3.0" } diff --git a/backend/package.json b/backend/package.json index c9c7f0cf..083ebde0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -53,6 +53,7 @@ "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", "socket.io": "^4.8.1", + "socket.io-mock": "^1.3.2", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" @@ -102,7 +103,6 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", - "socket.io-mock": "^1.3.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" diff --git a/backend/src/controllers/tests/1-clients.test.ts b/backend/src/controllers/tests/1-clients.test.ts index 9c801e16..e7351ab9 100644 --- a/backend/src/controllers/tests/1-clients.test.ts +++ b/backend/src/controllers/tests/1-clients.test.ts @@ -9,7 +9,7 @@ import { ObjectID } from 'mongodb' import { IClient } from '../../models/clients' import { Jwt } from '../../models/technical' import { saveClient } from '../clients'; -import SocketMock from 'socket.io-mock'; +import MockedSocket from 'socket.io-mock'; const fakeUser: Jwt = { data: { @@ -45,7 +45,7 @@ describe('clients controller :: saveClient creation', () => { user: fakeUser, body: {...fakeClient, name: 'Company X'}, db: fakeDb, - io: new SocketMock(), + io: new MockedSocket(), } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/2-consultants.test.ts b/backend/src/controllers/tests/2-consultants.test.ts index 6412b069..cb662fc7 100644 --- a/backend/src/controllers/tests/2-consultants.test.ts +++ b/backend/src/controllers/tests/2-consultants.test.ts @@ -11,7 +11,7 @@ import { Jwt } from '../../models/technical' import { saveConsultant } from '../consultants'; import { IConsultant } from '../../models/consultants'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import SocketMock from 'socket.io-mock'; +import MockedSocket from 'socket.io-mock'; const fakeUser: Jwt = { data: { _id: '_id', email: 'string', firstName: 'first', name: 'name', alias: 'alias', active: true }, @@ -30,7 +30,7 @@ const createFakeRequestAndResponse = (db: Db, consultant: Partial | user: fakeUser, body: {...fakeConsultant, ...(consultant || {})}, db, - io: new SocketMock(), + io: new MockedSocket(), } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/3-projectsMonth.test.ts b/backend/src/controllers/tests/3-projectsMonth.test.ts index 9dd50f9a..8fbb1636 100644 --- a/backend/src/controllers/tests/3-projectsMonth.test.ts +++ b/backend/src/controllers/tests/3-projectsMonth.test.ts @@ -12,7 +12,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import SocketMock from 'socket.io-mock'; +import MockedSocket from 'socket.io-mock'; @@ -22,7 +22,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new SocketMock(); + req.io = new MockedSocket(); next(); }); app.use('/', projectsRouter); diff --git a/backend/src/controllers/tests/4-projectsMonth.test.ts b/backend/src/controllers/tests/4-projectsMonth.test.ts index 04751801..fc44fc5d 100644 --- a/backend/src/controllers/tests/4-projectsMonth.test.ts +++ b/backend/src/controllers/tests/4-projectsMonth.test.ts @@ -11,7 +11,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import SocketMock from 'socket.io-mock'; +import MockedSocket from 'socket.io-mock'; @@ -21,7 +21,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new SocketMock(); + req.io = new MockedSocket(); next(); }); app.use('/', projectsRouter); From b04b2d21e74770fa7d278fae762ae0ee732f00dc Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 15:23:23 +0100 Subject: [PATCH 26/27] temp fix tests --- backend/package-lock.json | 113 +++++++++++++++--- backend/package.json | 2 +- .../src/controllers/tests/1-clients.test.ts | 4 +- .../controllers/tests/2-consultants.test.ts | 4 +- .../controllers/tests/3-projectsMonth.test.ts | 4 +- .../controllers/tests/4-projectsMonth.test.ts | 4 +- backend/src/models/technical.ts | 1 - backend/types/express/index.d.ts | 2 +- 8 files changed, 104 insertions(+), 30 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 17a0ba3c..cc3ab843 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -35,7 +35,6 @@ "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", "socket.io": "^4.8.1", - "socket.io-mock": "^1.3.2", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" @@ -85,6 +84,7 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", + "socket.io-mock-ts": "^1.0.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" @@ -3991,6 +3991,13 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "node_modules/@types/component-emitter": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.14.tgz", + "integrity": "sha512-lmPil1g82wwWg/qHSxMWkSKyJGQOK+ejXeMAAWyxNtVUD0/Ycj2maL63RAqpxVfdtvTfZkRnqzB0A9ft59y69g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -5434,6 +5441,16 @@ "node": ">=0.10.0" } }, + "node_modules/base/node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/base/node_modules/define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -5820,6 +5837,16 @@ "node": ">=0.10.0" } }, + "node_modules/cache-base/node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -6317,11 +6344,6 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -16646,15 +16668,27 @@ "ws": "~8.17.1" } }, - "node_modules/socket.io-mock": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", - "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", + "node_modules/socket.io-mock-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/socket.io-mock-ts/-/socket.io-mock-ts-1.0.2.tgz", + "integrity": "sha512-0XqMcm6+wDIPRBss3KDoRXV+Fcf4nissrHw9z9So4ZyWwwtKKSaj1gsvrJyGvYbhH+cGWeVgQx6KpaybgSp4hw==", + "dev": true, "license": "MIT", "dependencies": { + "@types/component-emitter": "^1.2.11", "component-emitter": "^1.3.0" } }, + "node_modules/socket.io-mock-ts/node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -17192,6 +17226,16 @@ "node": ">=6.4.0 <13 || >=14" } }, + "node_modules/superagent/node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/superagent/node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -22024,6 +22068,12 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "@types/component-emitter": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.14.tgz", + "integrity": "sha512-lmPil1g82wwWg/qHSxMWkSKyJGQOK+ejXeMAAWyxNtVUD0/Ycj2maL63RAqpxVfdtvTfZkRnqzB0A9ft59y69g==", + "dev": true + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -23168,6 +23218,12 @@ "pascalcase": "^0.1.1" }, "dependencies": { + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -23460,6 +23516,14 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + } } }, "call-bind": { @@ -23830,11 +23894,6 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -31835,12 +31894,22 @@ "ws": "~8.17.1" } }, - "socket.io-mock": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz", - "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==", + "socket.io-mock-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/socket.io-mock-ts/-/socket.io-mock-ts-1.0.2.tgz", + "integrity": "sha512-0XqMcm6+wDIPRBss3KDoRXV+Fcf4nissrHw9z9So4ZyWwwtKKSaj1gsvrJyGvYbhH+cGWeVgQx6KpaybgSp4hw==", + "dev": true, "requires": { + "@types/component-emitter": "^1.2.11", "component-emitter": "^1.3.0" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + } } }, "socket.io-parser": { @@ -32273,6 +32342,12 @@ "semver": "^7.3.8" }, "dependencies": { + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 083ebde0..a096d4ac 100644 --- a/backend/package.json +++ b/backend/package.json @@ -53,7 +53,6 @@ "regenerator-runtime": "^0.13.3", "slugify": "^1.1.0", "socket.io": "^4.8.1", - "socket.io-mock": "^1.3.2", "tmp": "^0.2.1", "ubl-builder": "github:pipesanta/ubl-builder", "uuid": "^10.0.0" @@ -103,6 +102,7 @@ "lint-staged": "^10.0.1", "mongodb-memory-server": "^8.12.1", "nodemon": "^1.11.0", + "socket.io-mock-ts": "^1.0.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", "typescript": "4.3" diff --git a/backend/src/controllers/tests/1-clients.test.ts b/backend/src/controllers/tests/1-clients.test.ts index e7351ab9..4a37334a 100644 --- a/backend/src/controllers/tests/1-clients.test.ts +++ b/backend/src/controllers/tests/1-clients.test.ts @@ -9,7 +9,7 @@ import { ObjectID } from 'mongodb' import { IClient } from '../../models/clients' import { Jwt } from '../../models/technical' import { saveClient } from '../clients'; -import MockedSocket from 'socket.io-mock'; +import { SocketServerMock } from 'socket.io-mock-ts'; const fakeUser: Jwt = { data: { @@ -45,7 +45,7 @@ describe('clients controller :: saveClient creation', () => { user: fakeUser, body: {...fakeClient, name: 'Company X'}, db: fakeDb, - io: new MockedSocket(), + io: new SocketServerMock() as any, } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/2-consultants.test.ts b/backend/src/controllers/tests/2-consultants.test.ts index cb662fc7..d4fec7c2 100644 --- a/backend/src/controllers/tests/2-consultants.test.ts +++ b/backend/src/controllers/tests/2-consultants.test.ts @@ -11,7 +11,7 @@ import { Jwt } from '../../models/technical' import { saveConsultant } from '../consultants'; import { IConsultant } from '../../models/consultants'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import MockedSocket from 'socket.io-mock'; +import { SocketServerMock } from 'socket.io-mock-ts'; const fakeUser: Jwt = { data: { _id: '_id', email: 'string', firstName: 'first', name: 'name', alias: 'alias', active: true }, @@ -30,7 +30,7 @@ const createFakeRequestAndResponse = (db: Db, consultant: Partial | user: fakeUser, body: {...fakeConsultant, ...(consultant || {})}, db, - io: new MockedSocket(), + io: new SocketServerMock() as any, } as ConfacRequest; const res = { diff --git a/backend/src/controllers/tests/3-projectsMonth.test.ts b/backend/src/controllers/tests/3-projectsMonth.test.ts index 8fbb1636..0bbb056e 100644 --- a/backend/src/controllers/tests/3-projectsMonth.test.ts +++ b/backend/src/controllers/tests/3-projectsMonth.test.ts @@ -12,7 +12,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import MockedSocket from 'socket.io-mock'; +import { SocketServerMock } from 'socket.io-mock-ts'; @@ -22,7 +22,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new MockedSocket(); + req.io = new SocketServerMock() as any;; next(); }); app.use('/', projectsRouter); diff --git a/backend/src/controllers/tests/4-projectsMonth.test.ts b/backend/src/controllers/tests/4-projectsMonth.test.ts index fc44fc5d..eb211e23 100644 --- a/backend/src/controllers/tests/4-projectsMonth.test.ts +++ b/backend/src/controllers/tests/4-projectsMonth.test.ts @@ -11,7 +11,7 @@ import request from 'supertest'; import express from 'express'; import projectsRouter from '../../routes/projects'; import bodyParser from 'body-parser'; -import MockedSocket from 'socket.io-mock'; +import { SocketServerMock } from 'socket.io-mock-ts'; @@ -21,7 +21,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new MockedSocket(); + req.io = new SocketServerMock() as any; next(); }); app.use('/', projectsRouter); diff --git a/backend/src/models/technical.ts b/backend/src/models/technical.ts index 28705981..9c7c1346 100644 --- a/backend/src/models/technical.ts +++ b/backend/src/models/technical.ts @@ -1,5 +1,4 @@ import {Request} from 'express'; -import { Server } from 'socket.io'; export type Jwt = { data: { diff --git a/backend/types/express/index.d.ts b/backend/types/express/index.d.ts index a12eadbd..d709f0fc 100644 --- a/backend/types/express/index.d.ts +++ b/backend/types/express/index.d.ts @@ -1,5 +1,5 @@ import {Db} from 'mongodb'; -import { Server } from 'socket.io'; +import {Server} from 'socket.io'; declare global { namespace Express { From a4eb864a0af34e50ce4b5ad6d79ea867cec7bb53 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 31 Dec 2024 15:25:12 +0100 Subject: [PATCH 27/27] minor clean --- backend/src/controllers/tests/3-projectsMonth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/controllers/tests/3-projectsMonth.test.ts b/backend/src/controllers/tests/3-projectsMonth.test.ts index 0bbb056e..90cbfa0a 100644 --- a/backend/src/controllers/tests/3-projectsMonth.test.ts +++ b/backend/src/controllers/tests/3-projectsMonth.test.ts @@ -22,7 +22,7 @@ const app = express(); app.use(bodyParser.json()); app.use((req: Request, res: Response, next: NextFunction) => { req.db = getFakeDb(); - req.io = new SocketServerMock() as any;; + req.io = new SocketServerMock() as any; next(); }); app.use('/', projectsRouter);