diff --git a/.env b/.env index dab6736..f7cfbd6 100644 --- a/.env +++ b/.env @@ -11,4 +11,10 @@ MONGO_AUTH_DB=admin API_PORT=5000 SOCKET_PORT=8000 NODE_ENV=development -FRONTEND_ORIGIN_PROD=http://185.100.212.76:8200 \ No newline at end of file +FRONTEND_ORIGIN_PROD=http://185.100.212.76:8200 + +# MinIO +MinIO_URL=185.100.212.76 +MinIO_PORT=9999 +MinIO_accessKey=sabarinathan +MinIO_secretKey=sabarinathan diff --git a/package-lock.json b/package-lock.json index 3e98cae..fb2e2c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "jsonwebtoken": "^9.0.2", "minio": "^8.0.5", "mongoose": "^8.8.1", + "multer": "^1.4.5-lts.2", "socket.io": "^4.8.1", "swagger-autogen": "^2.23.7", "swagger-ui-express": "^5.0.1" @@ -28,6 +29,8 @@ "@types/express": "^5.0.0", "@types/ip": "^1.1.3", "@types/jsonwebtoken": "^9.0.7", + "@types/mime-types": "^2.1.4", + "@types/multer": "^1.4.12", "@types/node": "^22.9.0", "@types/swagger-ui-express": "^4.1.7", "nodemon": "^3.1.7", @@ -207,12 +210,27 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.10.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.10.tgz", @@ -332,6 +350,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -474,6 +497,22 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -555,6 +594,47 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -587,6 +667,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1279,6 +1364,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1467,6 +1557,14 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minio": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.5.tgz", @@ -1499,6 +1597,17 @@ "node": ">= 10" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mongodb": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", @@ -1624,6 +1733,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1771,6 +1897,11 @@ "node": ">= 0.4" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2225,6 +2356,14 @@ "stream-chain": "^2.2.5" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -2411,6 +2550,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -2578,6 +2722,14 @@ "node": ">=4.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 2387750..b39f925 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "jsonwebtoken": "^9.0.2", "minio": "^8.0.5", "mongoose": "^8.8.1", + "multer": "^1.4.5-lts.2", "socket.io": "^4.8.1", "swagger-autogen": "^2.23.7", "swagger-ui-express": "^5.0.1" @@ -32,6 +33,8 @@ "@types/express": "^5.0.0", "@types/ip": "^1.1.3", "@types/jsonwebtoken": "^9.0.7", + "@types/mime-types": "^2.1.4", + "@types/multer": "^1.4.12", "@types/node": "^22.9.0", "@types/swagger-ui-express": "^4.1.7", "nodemon": "^3.1.7", diff --git a/src/api-server/Routes/projectRoutes.ts b/src/api-server/Routes/projectRoutes.ts index 92a3862..80016d9 100644 --- a/src/api-server/Routes/projectRoutes.ts +++ b/src/api-server/Routes/projectRoutes.ts @@ -1,6 +1,7 @@ import * as express from "express"; -import { createProjectController } from "../controller/project/projectController.ts"; +import { createProjectController, GetProjects } from "../controller/project/projectController.ts"; const projectRouter = express.Router(); projectRouter.post("/upsertProject",createProjectController) +projectRouter.get("/Projects/:userId/:organization",GetProjects) export default projectRouter \ No newline at end of file diff --git a/src/api-server/app.ts b/src/api-server/app.ts index 006d302..7e47001 100644 --- a/src/api-server/app.ts +++ b/src/api-server/app.ts @@ -53,7 +53,11 @@ app.use(cors()); // }, // credentials: true // })); -app.use(express.json()); + +app.use(express.json({ limit: "50mb" })); +app.use( + express.urlencoded({ limit: "50mb", extended: true, parameterLimit: 50000 }) +); dotenv.config(); app.get("/", (req, res) => { res.send("Hello, I am Major-Dwinzo API!"); @@ -62,6 +66,7 @@ app.get("/", (req, res) => { app.get("/health", (req, res) => { res.status(200).json({ message: "Server is running" }); }); + app.use("/api/v1", cameraRoutes); app.use("/api/v1", environmentsRoutes); app.use("/api/v1", linesRoutes); diff --git a/src/api-server/controller/project/projectController.ts b/src/api-server/controller/project/projectController.ts index 0d4b8cc..c9e8bef 100644 --- a/src/api-server/controller/project/projectController.ts +++ b/src/api-server/controller/project/projectController.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { createProject } from "../../../shared/services/project/project-Services.ts"; +import { createProject,GetAllProjects } from "../../../shared/services/project/project-Services.ts"; export const createProjectController = async ( req: Request, @@ -7,7 +7,6 @@ export const createProjectController = async ( ): Promise => { try { const result = await createProject(req.body); - console.log("result: ", result); switch (result.status) { case "project_exists": @@ -45,3 +44,40 @@ export const createProjectController = async ( }); } }; +export const GetProjects = async ( + req: Request, + res: Response +): Promise => { + try { + const { userId, organization } = req.params; + const result = await GetAllProjects({ userId, organization}); + switch (result?.status) { + + case "User not found": + res.status(404).json({ + message: "User not found", + }); + break; + + case "success": + res.status(201).json({ + Projects: result?.Datas, + }); + break; + case "All fields are required": + res.status(400).json({ + message: "All fields are required", + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "Unknown error", + }); + } +}; \ No newline at end of file diff --git a/src/shared/connect/blobConnection.ts b/src/shared/connect/blobConnection.ts new file mode 100644 index 0000000..414ae14 --- /dev/null +++ b/src/shared/connect/blobConnection.ts @@ -0,0 +1,10 @@ +import { Client } from 'minio'; +const minioClient = new Client({ + endPoint: process.env.MinIO_URL!, + port: parseInt(process.env.MinIO_PORT!, 10), + useSSL: false, + accessKey: process.env.MinIO_accessKey!, + secretKey: process.env.MinIO_secretKey!, +}); + + export { minioClient} \ No newline at end of file diff --git a/src/shared/connect/mongoose.ts b/src/shared/connect/mongoose.ts index 6548369..ffba38c 100644 --- a/src/shared/connect/mongoose.ts +++ b/src/shared/connect/mongoose.ts @@ -49,12 +49,6 @@ const MainModel = ( throw error; } }; -export const minioClient = new Client({ - endPoint: "185.100.212.76", // MinIO server IP or hostname - port: 9999, // MinIO server port - useSSL: false, // Set to true if SSL is configured - accessKey: "sabarinathan", // Access key - secretKey: "sabarinathan", -}); + export default MainModel; diff --git a/src/shared/model/project/project-model.ts b/src/shared/model/project/project-model.ts index 504ad3d..3320391 100644 --- a/src/shared/model/project/project-model.ts +++ b/src/shared/model/project/project-model.ts @@ -15,7 +15,7 @@ export interface Project extends Document { } const projectSchema: Schema = new Schema( { - projectUuid: { type: String, required: true }, + projectUuid: { type: String }, projectName: { type: String }, thumbnail: { type: String }, isArchive: { type: Boolean, default: false }, diff --git a/src/shared/services/blob/blobServices.ts b/src/shared/services/blob/blobServices.ts new file mode 100644 index 0000000..87df4d0 --- /dev/null +++ b/src/shared/services/blob/blobServices.ts @@ -0,0 +1,67 @@ +import { minioClient } from "../../connect/blobConnection.ts"; + +type MulterFile = { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + size: number; + buffer: Buffer; +}; + +const bucketName = "assets-public-bucket"; + +// Define public read policy +const publicReadPolicy = { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { AWS: "*" }, + Action: ["s3:GetObject"], + Resource: [`arn:aws:s3:::${bucketName}/*`], + }, + ], +}; + +async function ensureBucketExists() { + const exists = await minioClient.bucketExists(bucketName); + if (!exists) { + await minioClient.makeBucket(bucketName, "local-region"); + console.log("Bucket created"); + } +} + +async function ensureFolderExists(folderName: string) { + const folderPrefix = folderName.endsWith("/") ? folderName : `${folderName}/`; + const objects = minioClient.listObjects(bucketName, folderPrefix, true); + for await (const _ of objects) return; // Folder exists + await minioClient.putObject(bucketName, folderPrefix, Buffer.from("")); +} + +async function setPublicPolicy() { + await minioClient.setBucketPolicy(bucketName, JSON.stringify(publicReadPolicy)); +} + +export async function uploadProjectThumbnail(file: MulterFile, folderName: string): Promise { + try { + const folderName = "models"; + const objectName = `${folderName}/${Date.now()}_${file.originalname}`; + + + await ensureBucketExists(); + await ensureFolderExists(folderName); + await minioClient.putObject(bucketName, objectName, file.buffer, file.size, { + "Content-Type": file.mimetype, + }); + await setPublicPolicy(); + + const encodedName = encodeURIComponent(objectName); + const blobUrl = `${process.env.MinIO_USESSL === "true" ? "https" : "http"}://${process.env.MinIO_URL}:${process.env.MINIO_PORT}/${bucketName}/${encodedName}`; + + return blobUrl; + } catch (err) { + console.error("Upload failed:", err); + throw new Error("Thumbnail upload failed"); + } +} diff --git a/src/shared/services/project/project-Services.ts b/src/shared/services/project/project-Services.ts index 21975f9..90b8ac2 100644 --- a/src/shared/services/project/project-Services.ts +++ b/src/shared/services/project/project-Services.ts @@ -2,6 +2,8 @@ import projectModel from "../../model/project/project-model.ts"; import userModel from "../../model/user-Model.ts"; import { Types } from "mongoose"; import versionModel from "../../model/version/versionModel.ts"; +import { uploadProjectThumbnail } from "../blob/blobServices.ts"; + interface CreateProjectInput { projectName: string; projectUuid: string; @@ -10,7 +12,10 @@ interface CreateProjectInput { sharedUsers?: string[]; organization: string; } - +interface GetInterface { + userId: string; + organization: string; +} export const createProject = async (data: CreateProjectInput) => { try { const { @@ -21,9 +26,9 @@ export const createProject = async (data: CreateProjectInput) => { sharedUsers, organization, } = data; + if ( !projectName || - !projectUuid || !userId || !thumbnail || // !sharedUsers || @@ -36,7 +41,7 @@ export const createProject = async (data: CreateProjectInput) => { status: "user_not_found", }; } - const projectExisting = await existingProject(projectUuid, organization); + const projectExisting = await existingProject(projectName, organization,userId); if (projectExisting) { return { @@ -49,17 +54,18 @@ export const createProject = async (data: CreateProjectInput) => { projectName: projectName, projectUuid: projectUuid, createdBy: userId, - thumbnail: thumbnail || "", + thumbnail: thumbnail, sharedUsers: sharedUsers || [], isArchive: false, }); - const versionData = previousVersion(project._id, organization); - if (!versionData) { - await versionModel(organization).create({ + const versionData = await previousVersion(project._id, organization); + if (!versionData || versionData.length === 0) { + const newVersion= await versionModel(organization).create({ projectId: project._id, createdBy: userId, version: 0.01, }); + await projectModel(organization).findByIdAndUpdate({_id:project._id,isArchive:false},{total_versions:`v-${newVersion.version.toFixed(2)}`}) } return { status: "success", @@ -73,7 +79,7 @@ export const createProject = async (data: CreateProjectInput) => { } }; -export const GetAllProjects = async (data: CreateProjectInput) => { +export const GetAllProjects = async (data: GetInterface) => { try { const { userId, organization } = data; await existingUser(userId, organization); @@ -83,18 +89,20 @@ export const GetAllProjects = async (data: CreateProjectInput) => { isArchive: false, }) .select("_id projectName createdBy thumbnail"); - if (projectDatas) return { Datas: projectDatas }; + if (projectDatas) return {status:"success", Datas: projectDatas }; } catch (error: unknown) { return { status: error }; } }; export const existingProject = async ( - projectUuid: string, - organization: string + projectName: string, + organization: string, + userId:string ) => { const projectData = await projectModel(organization).findOne({ - projectUuid: projectUuid, + projectName: projectName, + createdBy:userId, isArchive: false, }); return projectData; @@ -124,12 +132,10 @@ export const archiveProject = async ( export const previousVersion = async ( projectId: string, organization: string -): Promise => { - const result = await versionModel(organization) - .findOne({ - projectId: projectId, - isArchive: false, - }) - .sort({ version: -1 }); +)=> { + console.log('projectId: ', projectId); + const result = await versionModel(organization).findOne({ projectId: projectId, isArchive: false}) + console.log('result: ', result); + // .sort({ version: -1 }); return result; }; diff --git a/src/socket-server/controllers/project/projectController.ts b/src/socket-server/controllers/project/projectController.ts index b046cf1..df4ffa3 100644 --- a/src/socket-server/controllers/project/projectController.ts +++ b/src/socket-server/controllers/project/projectController.ts @@ -1,15 +1,17 @@ -import { Socket } from "socket.io"; +import { Socket, Server } from "socket.io"; import { createProject } from "../../../shared/services/project/project-Services.ts"; import { EVENTS } from "../../socket/events.ts"; +import { emitEventResponse } from "../../utils/emitEventResponse.ts"; export const projectHandleEvent = async ( event: string, socket: Socket, data: any, - namespace: any + namespace: any, + io: Server // Make sure this is passed in from the socket manager ) => { if (!data?.organization) { - console.warn(`Missing organization in event: ${event}`); + console.log(`Missing organization in event: ${event}`); return; } @@ -18,29 +20,29 @@ export const projectHandleEvent = async ( switch (event) { case EVENTS.addProject: { result = await createProject(data); - - // Create response object + console.log('result: ', result); + const isSuccess = result.status === "success"; + // ✅ Build response object const response = { success: result.status === "success", message: result.status === "project_exists" ? "Project already exists" : result.status === "user_not_found" - ? "User not found" - : result.status === "invalid_user_id" - ? "Invalid User ID" - : result.status === "success" - ? "Project created successfully" - : "Something went wrong", - data: result.project || null, - status: result.status, + ? "User not found" + : result.status === "invalid_user_id" + ? "Invalid User ID" + : isSuccess + ? "Project created successfully" + : "Something went wrong", + ...(isSuccess ? { data: result.project } : {}), + status: String(result.status), socketId: socket.id, organization: data.organization, }; - // Emit response to the organization room - - + // ✅ Emit response + emitEventResponse(socket, data.organization, EVENTS.projectResponse, response); break; } diff --git a/src/socket-server/index.ts b/src/socket-server/index.ts index 044f064..88dbdbd 100644 --- a/src/socket-server/index.ts +++ b/src/socket-server/index.ts @@ -7,7 +7,7 @@ import dotenv from "dotenv"; // Import dotenv dotenv.config(); import { initSocketServer } from "./socket/socketManager.ts"; - +import { SocketServer } from "./manager/manager.ts"; const app = express(); const PORT = process.env.SOCKET_PORT; const server = http.createServer(app); @@ -15,8 +15,8 @@ const server = http.createServer(app); app.get('/', (req: Request, res: Response) => { res.send('Hello, I am Major-Dwinzo RealTime!'); }); - initSocketServer(server); +// SocketServer(server) server.listen(PORT, () => { console.log(`socket-Server is running on http://localhost:${PORT}`); }); diff --git a/src/socket-server/manager/manager.ts b/src/socket-server/manager/manager.ts new file mode 100644 index 0000000..1ef2dc8 --- /dev/null +++ b/src/socket-server/manager/manager.ts @@ -0,0 +1,78 @@ +import { Server, Socket } from 'socket.io'; +import { projectHandleEvent } from '../controllers/project/projectController.ts'; + +export const SocketServer = (httpServer: any) => { + const io = new Server(httpServer, { + cors: { + origin: '*', // Allow CORS for all origins (adjust in production) + methods: ['GET', 'POST'], + }, + }); + + + + const namespaces = { + + project: io.of('/project'), + + }; + + // const onlineUsers = new Map>(); + const onlineUsers: { [organization: string]: Set } = {}; + + + const handleNamespace = ( namespace: any, ...eventHandlers: Function[]) => { + + namespace.on("connection", (socket: Socket) => { + console.log(`✅ Client connected to ${namespace.name}: ${socket.id}`); + // Extract organization from query parameters + + const organization = socket.handshake.query.organization as string; + const email = socket.handshake.query.email as string; + // const {organization,email} = socket.handshake.auth + console.log(`🔍 Received organization: ${organization}`); + + if (organization) { + socket.join(organization); + // console.log(`🔹 Socket ${socket.id} joined room: ${organization}`); + } + // Handle all events + if (organization && email) { + if (!onlineUsers[organization]) { + onlineUsers[organization] = new Set(); + } + onlineUsers[organization].add(socket.id); + + } + + // userStatus(EVENTS.connection, socket, socket.handshake.auth, socket); + + socket.onAny((event: string, data: any ,callback:any) => { + eventHandlers.forEach(handler => handler(event, socket, data, namespace,io,callback)); + }); + + // Handle disconnection + socket.on("disconnect", () => { + onlineUsers[organization]?.delete(socket.id); + if (onlineUsers[organization]?.size === 0) delete onlineUsers[organization]; + // userStatus(EVENTS.disconnect, socket, socket.handshake.auth, socket); + + }); + + // Handle reconnection (Auto rejoin) + socket.on("reconnect", (attempt: number) => { + + if (organization) { + socket.join(organization); + + } + }); + }); + }; + + + + +handleNamespace(namespaces.project,projectHandleEvent) + return io; +}; \ No newline at end of file diff --git a/src/socket-server/socket/events.ts b/src/socket-server/socket/events.ts index 1fee1b1..6d0dc31 100644 --- a/src/socket-server/socket/events.ts +++ b/src/socket-server/socket/events.ts @@ -101,5 +101,5 @@ export const EVENTS = { //PROJECT addProject:"v1:project:add", - projectResponse:"v1-project:response:update:", + projectResponse:"v1-project:response:update", } \ No newline at end of file diff --git a/src/socket-server/utils/emitEventResponse.ts b/src/socket-server/utils/emitEventResponse.ts new file mode 100644 index 0000000..8546ef8 --- /dev/null +++ b/src/socket-server/utils/emitEventResponse.ts @@ -0,0 +1,36 @@ +import { Socket } from "socket.io"; + +interface EmitOptions { + success: boolean; + message: string; + data?: any; + error?: any; + organization: string; + socketId: string; + status?: string; +} + +/** + * Emit a structured socket event response to a specific organization room. + */ +export const emitEventResponse = ( + socket: Socket, + organization: string, + event: string, + result: EmitOptions +) => { + console.log(`📤 emitEventResponse called for event: ${event}, organization: ${organization}`); + if (!organization) { + console.log(`Organization missing in response for event: ${event}`); + return; + } + + socket.emit(event, { + success: result.success, + message: result.message, + data: result.data , + error: result.error ?? null, + socketId: result.socketId, + organization, + }); +};