diff --git a/.env b/.env index f7cfbd6..122147a 100644 --- a/.env +++ b/.env @@ -18,3 +18,15 @@ MinIO_URL=185.100.212.76 MinIO_PORT=9999 MinIO_accessKey=sabarinathan MinIO_secretKey=sabarinathan + + +# token +JWT_SECRET="DwinzoProject" +REFRESH_JWT_SECRET="RefreshDwinzoProject" + +# redis +REDIS_ENV= true +REDIS_LOCAL =127.0.0.1 +REDIS_PORT=6379 +# REDIS_DOCKER =185.100.212.76 +# REDIS_PORT=6666 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fb2e2c2..bc6d21c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,13 @@ "express": "^4.21.1", "fs": "^0.0.1-security", "http": "^0.0.1-security", + "ioredis": "^5.6.1", "ip": "^2.0.1", "jsonwebtoken": "^9.0.2", "minio": "^8.0.5", "mongoose": "^8.8.1", "multer": "^1.4.5-lts.2", + "nodemailer": "^7.0.3", "socket.io": "^4.8.1", "swagger-autogen": "^2.23.7", "swagger-ui-express": "^5.0.1" @@ -32,6 +34,7 @@ "@types/mime-types": "^2.1.4", "@types/multer": "^1.4.12", "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.17", "@types/swagger-ui-express": "^4.1.7", "nodemon": "^3.1.7", "ts-node": "^10.9.2", @@ -50,6 +53,11 @@ "node": ">=12" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -239,6 +247,15 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.18", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", @@ -589,6 +606,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -730,6 +755,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1235,6 +1268,50 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ioredis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ioredis/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", @@ -1438,11 +1515,21 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -1758,6 +1845,14 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", @@ -2006,6 +2101,25 @@ "node": ">=8.10.0" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2335,6 +2449,11 @@ "node": ">=6" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index b39f925..29bba9f 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "express": "^4.21.1", "fs": "^0.0.1-security", "http": "^0.0.1-security", + "ioredis": "^5.6.1", "ip": "^2.0.1", "jsonwebtoken": "^9.0.2", "minio": "^8.0.5", "mongoose": "^8.8.1", "multer": "^1.4.5-lts.2", + "nodemailer": "^7.0.3", "socket.io": "^4.8.1", "swagger-autogen": "^2.23.7", "swagger-ui-express": "^5.0.1" @@ -36,6 +38,7 @@ "@types/mime-types": "^2.1.4", "@types/multer": "^1.4.12", "@types/node": "^22.9.0", + "@types/nodemailer": "^6.4.17", "@types/swagger-ui-express": "^4.1.7", "nodemon": "^3.1.7", "ts-node": "^10.9.2", diff --git a/src/api-server/V1/v1Controllers/authController/authControllers.ts b/src/api-server/V1/v1Controllers/authController/authControllers.ts new file mode 100644 index 0000000..710b4d4 --- /dev/null +++ b/src/api-server/V1/v1Controllers/authController/authControllers.ts @@ -0,0 +1,245 @@ +import { Request, Response } from "express"; +import { + AuthLogin, + AuthLogout, + AuthSignup, + forgetPassword, +} from "../../../../shared/services/auth/authServices.ts"; + +export const SignupController = async ( + req: Request, + res: Response +): Promise => { + try { + console.log("req.body: ", req.body); + const { userName, Email, Password, profilePicture } = req.body; + if (!userName || !Email || !Password) { + res.status(400).json({ + message: "All fields are required", + }); + return; + } + const result = await AuthSignup(req.body); + + switch (result.status) { + case "User already exists": + res.status(403).json({ + message: "User already exists", + }); + break; + + case "Success": + res.status(201).json({ + message: "New User created", + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; +export const SignInController = async ( + req: Request, + res: Response +): Promise => { + try { + const { Email, Password, fingerprint } = req.body; + if (!fingerprint || !Email || !Password) { + res.status(400).json({ + message: "All fields are required", + }); + return; + } + const result = await AuthLogin(req.body); + + switch (result.status) { + case "User Not Found!!! Kindly signup...": + res.status(404).json({ + message: "User Not Found!!! Kindly signup...", + }); + break; + case "Email & Password is invalid...Check the credentials": + res.status(400).json({ + message: "Email & Password is invalid...Check the credentials", + }); + break; + case "Already LoggedIn on another browser....Please logout!!!": + res.status(403).json({ + message: "Already LoggedIn on another browser....Please logout!!!", + }); + break; + + case "User_Datas not found": + res.status(404).json({ + message: "User_Datas not found", + }); + break; + case "User update failed.": + res.status(400).json({ + message: "User update failed.", + }); + break; + case "Success": + res.status(200).json({ + message: result.data, + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; +export const SignOutController = async ( + req: Request, + res: Response +): Promise => { + try { + const { Email } = req.body; + if (!Email) { + res.status(400).json({ + message: "Email field is Mandatory", + }); + return; + } + const result = await AuthLogout(req.body); + + switch (result.status) { + case "User not found": + res.status(404).json({ + message: "User not found", + }); + break; + case "Token not found": + res.status(404).json({ + message: "Token not found", + }); + break; + case "Success": + res.status(200).json({ + message: "Logout Successfull", + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; +export const ForgetPasswordController = async ( + req: Request, + res: Response +): Promise => { + try { + const { Email } = req.body; + if (!Email) { + res.status(400).json({ + message: "Email field is Mandatory", + }); + return; + } + const result = await forgetPassword(req.body); + switch (result.status) { + case "You can only reset your password once every 24 hours.": + res.status(400).json({ + message: "You can only reset your password once every 24 hours.", + }); + break; + case "Email not found": + res.status(404).json({ + message: "Email not found", + }); + break; + case "Success": + res.status(200).json({ + message: "Password reset link sent successfully", + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; +export const ResetPasswordController = async ( + req: Request, + res: Response +): Promise => { + try { + const { newPassword, resetToken, confirmPassword } = req.body; + if (!newPassword || !resetToken || !confirmPassword) { + res.status(400).json({ + message: "All fields are Mandatory", + }); + return; + } + const result = await forgetPassword(req.body); + + switch (result.status) { + case "Invalid token payload.": + res.status(400).json({ + message: "Invalid token payload.", + }); + break; + case "Password mismatch": + res.status(400).json({ + message: "Password mismatch", + }); + break; + case "User not found": + res.status(404).json({ + message: "User not found", + }); + break; + case "Token is invalid or expired.": + res.status(404).json({ + message: "Token is invalid or expired.", + }); + break; + case "Success": + res.status(200).json({ + message: "Password reset successfull!!", + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; diff --git a/src/api-server/V1/v1Controllers/versionController/versioncontroller.ts b/src/api-server/V1/v1Controllers/versionController/versioncontroller.ts new file mode 100644 index 0000000..7200793 --- /dev/null +++ b/src/api-server/V1/v1Controllers/versionController/versioncontroller.ts @@ -0,0 +1,31 @@ +import { Request, Response } from "express"; +import versionService from "../../../../shared/services/version/versionService.ts"; + +export const versioncontroller = async ( + req: Request, + res: Response +): Promise => { + try { + console.log("req.body: ", req.body); + const { projectId, userId, description, db } = req.body; + // if (!userName || !Email || !description) { + // res.status(400).json({ + // message: "All fields are required", + // }); + // return; + // } + const result = await versionService.saveCurrentStateAsVersion( + db, + projectId, + userId, + description + ); + + console.log(result); + } catch (error) { + res.status(500).json({ + message: "An unexpected error occurred", + }); + return; + } +}; diff --git a/src/api-server/V1/v1Routes/authRoutes.ts b/src/api-server/V1/v1Routes/authRoutes.ts new file mode 100644 index 0000000..34e5528 --- /dev/null +++ b/src/api-server/V1/v1Routes/authRoutes.ts @@ -0,0 +1,83 @@ +import express from "express"; +import { + ForgetPasswordController, + ResetPasswordController, + SignInController, + SignOutController, + SignupController, +} from "../v1Controllers/authController/authControllers.ts"; +import { versioncontroller } from "../v1Controllers/versionController/versioncontroller.ts"; +import { + createProjectController, + GetProjects, + RemoveProject, + updateProjectController, + ViewData, +} from "../../controller/project/projectController.ts"; +import { tokenValidator } from "../../../shared/utils/token.ts"; +import authorizedRoles from "../../../shared/middleware/rbacMiddleware.ts"; +import { recentDataController } from "../../controller/home/homeControllers.ts"; +import { + GetTrashList, + RestoreTrash, +} from "../../controller/trash/trashcontrollers.ts"; + +const Authrouter = express.Router(); +Authrouter.post("/Auth/signup", SignupController); +Authrouter.post("/Auth/login", SignInController); +Authrouter.post("/Auth/logout", SignOutController); +Authrouter.post("/Auth/forgetPassword", ForgetPasswordController); +Authrouter.post("/Auth/reset-password/:resetToken", ResetPasswordController); +Authrouter.post("/Auth/versionData", versioncontroller); + +// project +Authrouter.post("/Auth/upsertProject", tokenValidator, createProjectController); +Authrouter.get( + "/Auth/Projects", + tokenValidator, + authorizedRoles("Admin", "User"), + GetProjects +); +Authrouter.patch( + "/Auth/Project/archive/:projectId", + tokenValidator, + authorizedRoles("Admin", "User"), + RemoveProject +); + +Authrouter.patch( + "/Auth/Project/modify", + tokenValidator, + authorizedRoles("Admin", "User"), + updateProjectController +); +Authrouter.get( + "/Auth/Project/view", + tokenValidator, + authorizedRoles("Admin", "User"), + ViewData +); + +//home-Page +Authrouter.get( + "/Auth/RecentlyViewed", + tokenValidator, + authorizedRoles("Admin", "User"), + recentDataController +); + +//trash +Authrouter.get( + "/Auth/Trash/Lists", + tokenValidator, + authorizedRoles("Admin", "User"), + GetTrashList +); + +Authrouter.patch( + "/Auth/restore", + tokenValidator, + authorizedRoles("Admin", "User"), + RestoreTrash +); +export default Authrouter; diff --git a/src/api-server/app.ts b/src/api-server/app.ts index a4e996e..a3a8ae3 100644 --- a/src/api-server/app.ts +++ b/src/api-server/app.ts @@ -20,8 +20,10 @@ import productRouter from "./Routes/productRoutes.ts"; import projectRouter from "./Routes/projectRoutes.ts"; import trashRouter from "./Routes/trashRoutes.ts"; import homePageRouter from "./Routes/homepageRoutes.ts"; +import redis from "../shared/redis/redis.ts"; +import Authrouter from "./V1/v1Routes/authRoutes.ts"; // import productFlowRoutes from "./Routes/productFlowRouts.ts"; - +redis; const app = express(); app.use(cors()); // const allowedOriginsDev = [ @@ -87,4 +89,8 @@ app.use("/api/v2", productRouter); app.use("/api/v1", projectRouter); app.use("/api/v1", trashRouter); app.use("/api/v1", homePageRouter); + +//New versions +app.use("/API/V1", Authrouter); + export default app; diff --git a/src/api-server/controller/home/homeControllers.ts b/src/api-server/controller/home/homeControllers.ts index a608482..9907c9e 100644 --- a/src/api-server/controller/home/homeControllers.ts +++ b/src/api-server/controller/home/homeControllers.ts @@ -1,19 +1,20 @@ import { Request, Response } from "express"; import { RecentlyAdded, searchProject, searchTrashProject } from "../../../shared/services/home/homeService.ts"; +import { AuthenticatedRequest } from "../../../shared/utils/token.ts"; export const recentDataController = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { userId, organization } = req.params; - if (!userId || !organization) { + const { userId, organization,role } = req.user||{}; + if (!userId || !organization||!role) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await RecentlyAdded({ userId, organization }); + const result = await RecentlyAdded({ userId, organization,role }); switch (result.status) { case "User not found": diff --git a/src/api-server/controller/project/projectController.ts b/src/api-server/controller/project/projectController.ts index a061d11..c86a4ee 100644 --- a/src/api-server/controller/project/projectController.ts +++ b/src/api-server/controller/project/projectController.ts @@ -6,20 +6,27 @@ import { updateProject, viewProject, } from "../../../shared/services/project/project-Services.ts"; +import { AuthenticatedRequest } from "../../../shared/utils/token.ts"; export const createProjectController = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { projectUuid, userId, thumbnail, organization } = req.body; - if (!projectUuid || !userId || !thumbnail || !organization) { + const { userId, organization } = req.user || {}; + console.log("req.user: ", req.user); + const { projectUuid, thumbnail } = req.body; + if (!req.user || !req.user.userId || !req.user.organization) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + if (!projectUuid || !thumbnail) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await createProject(req.body); + const result = await createProject({ ...req.body, userId, organization }); switch (result.status) { case "project_exists": @@ -54,18 +61,19 @@ export const createProjectController = async ( } }; export const GetProjects = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { userId, organization } = req.params; - if (!userId || !organization) { + const { userId, organization, role } = req.user || {}; + // const { userId, organization } = req.params; + if (!userId || !organization || !role) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await GetAllProjects({ userId, organization }); + const result = await GetAllProjects({ userId, organization, role }); switch (result?.status) { case "User not found": res.status(404).json({ @@ -92,19 +100,34 @@ export const GetProjects = async ( } }; export const RemoveProject = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { const { projectId } = req.params; - const { organization, userId } = req.body; - if (!projectId || !organization || !userId) { + // const { organization, userId } = req.body; + const { organization, userId, role } = req.user || {}; + if ( + !req.user || + !req.user.userId || + !req.user.organization || + !req.user.role + ) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + if (!projectId || !organization || !userId || !role) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await DeleteProject({ projectId, organization, userId }); + const result = await DeleteProject({ + projectId, + organization, + userId, + role, + }); switch (result?.status) { case "Project not found": res.status(404).json({ @@ -135,13 +158,13 @@ export const RemoveProject = async ( } }; export const updateProjectController = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { projectId, organization, projectName, thumbnail, userId } = - req.body; - if (!userId || !organization || !projectId) { + const { userId, organization, role } = req.user || {}; + const { projectId, projectName, thumbnail } = req.body; + if (!userId || !organization || !projectId || !role) { res.status(400).json({ message: "All fields are required", }); @@ -153,6 +176,7 @@ export const updateProjectController = async ( userId, projectName, thumbnail, + role, }); switch (result?.status) { case "Project not found": @@ -184,14 +208,25 @@ export const updateProjectController = async ( return; } }; -export const ViewData = async (req: Request, res: Response): Promise => { +export const ViewData = async ( + req: AuthenticatedRequest, + res: Response +): Promise => { try { - const { projectId, organization, userId } = req.query as { - organization: string; + const { organization, userId, role } = req.user || {}; + if ( + !req.user || + !req.user.userId || + !req.user.organization || + !req.user.role + ) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + const { projectId } = req.query as { projectId: string; - userId: string; }; - if (!userId || !organization || !projectId) { + if (!userId || !organization || !projectId || !role) { res.status(400).json({ message: "All fields are required", }); @@ -201,6 +236,7 @@ export const ViewData = async (req: Request, res: Response): Promise => { projectId, organization, userId, + role, }); switch (result?.status) { case "Project not found": diff --git a/src/api-server/controller/trash/trashcontrollers.ts b/src/api-server/controller/trash/trashcontrollers.ts index 7b02590..bfc54ee 100644 --- a/src/api-server/controller/trash/trashcontrollers.ts +++ b/src/api-server/controller/trash/trashcontrollers.ts @@ -3,20 +3,21 @@ import { TrashDatas, RestoreTrashData, } from "../../../shared/services/trash/trashService.ts"; +import { AuthenticatedRequest } from "../../../shared/utils/token.ts"; export const GetTrashList = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { organization } = req.query as { organization: string }; - if (!organization) { + const { organization, role, userId } = req.user || {}; + if (!organization || !role || !userId) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await TrashDatas({ organization }); + const result = await TrashDatas({ organization, role, userId }); switch (result.status) { case "Trash is Empty": @@ -28,7 +29,6 @@ export const GetTrashList = async ( case "Success": res.status(200).json({ - // message: "Project created Successfully", TrashDatas: result.ListDatas, }); break; @@ -47,22 +47,26 @@ export const GetTrashList = async ( }; export const RestoreTrash = async ( - req: Request, + req: AuthenticatedRequest, res: Response ): Promise => { try { - const { organization, projectId } = req.query as { - organization: string; + const { organization, role, userId } = req.user || {}; + const { projectId } = req.query as { projectId: string; }; - console.log("organization: ", organization); - if (!organization || !projectId) { + if (!organization || !projectId || !role || !userId) { res.status(400).json({ message: "All fields are required", }); return; } - const result = await RestoreTrashData({ organization, projectId }); + const result = await RestoreTrashData({ + organization, + projectId, + role, + userId, + }); switch (result.status) { case "Project not found": diff --git a/src/api-server/controller/user-Controller.ts b/src/api-server/controller/user-Controller.ts index 01106b6..9ad5701 100644 --- a/src/api-server/controller/user-Controller.ts +++ b/src/api-server/controller/user-Controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import userModel from "../../shared/model/user-Model.ts"; -import {hashGenerate,hashValidator} from "../../shared/security/Hasing.ts" +import {hashGenerate,hashValidator} from "../../shared/utils/Hasing.ts" let serverAlive = true; export class User { diff --git a/src/shared/V1Models/Auth/tokenModel.ts b/src/shared/V1Models/Auth/tokenModel.ts index 94b93e4..30ed6fa 100644 --- a/src/shared/V1Models/Auth/tokenModel.ts +++ b/src/shared/V1Models/Auth/tokenModel.ts @@ -3,14 +3,14 @@ import MainModel from "../../connect/mongoose.ts"; import { User } from "./userAuthModel.ts"; export interface Token extends Document { userId: User["_id"]; - isArchieve: Boolean; + isArchive: Boolean; refreshToken: string; resetTokenExpiry?: Date; resetToken: string; } const tokenSchema: Schema = new Schema({ userId: { type: Schema.Types.ObjectId, ref: "User" }, - isArchieve: { type: Boolean, default: false }, + isArchive: { type: Boolean, default: false }, token: { type: String }, refreshToken: { type: String }, tokenCreatedAt: { type: Date }, diff --git a/src/shared/V1Models/Auth/user.ts b/src/shared/V1Models/Auth/user.ts index dba27bc..4988c43 100644 --- a/src/shared/V1Models/Auth/user.ts +++ b/src/shared/V1Models/Auth/user.ts @@ -3,41 +3,44 @@ import MainModel from "../../connect/mongoose.ts"; import { User } from "./userAuthModel.ts"; export interface UserData extends Document { userId: User["_id"]; + isShare: Boolean; + activeStatus: string; notificationEnable: boolean; About: string; - isArchieve: boolean; - Role: string; - Profilepicture: string; + isArchive: boolean; + role: string; + profilePicture: string; recentlyViewed: string[]; - CheckIn: number; - CheckOut: number; } const UserDataSchema: Schema = new Schema({ userId: { type: Schema.Types.ObjectId, ref: "User" }, - isArchieve: { type: Boolean, default: false }, + isArchive: { type: Boolean, default: false }, notificationEnable: { type: Boolean, default: false }, About: { type: String, }, - Role: { + role: { type: String, default: "User", enum: ["User", "Admin"], }, + isShare: { + type: Boolean, + default: false, + }, + activeStatus: { + type: String, + enum: ["online", "offline"], + default: "offline", + }, recentlyViewed: { type: [String], default: [], }, - Profilepicture: { + profilePicture: { type: String, // default: "default-profile-picture.jpg" }, - CheckIn: { - type: Number, - }, - CheckOut: { - type: Number, - }, }); const UsersDataModel = (db: any) => { diff --git a/src/shared/V1Models/Auth/userAuthModel.ts b/src/shared/V1Models/Auth/userAuthModel.ts index d1ce57b..c85e3a0 100644 --- a/src/shared/V1Models/Auth/userAuthModel.ts +++ b/src/shared/V1Models/Auth/userAuthModel.ts @@ -1,15 +1,15 @@ import { Schema, Document } from "mongoose"; import MainModel from "../../connect/mongoose.ts"; export interface User extends Document { - Username: string; + userName: string; Email: string; Password: string; - isArchieve: boolean; + isArchive: boolean; visitorBrowserID: string; lastPasswordReset: number; } -const signupschema: Schema = new Schema({ - Username: { +const AuthSchema: Schema = new Schema({ + userName: { type: String, required: true, }, @@ -23,12 +23,12 @@ const signupschema: Schema = new Schema({ min: 8, // required: true, }, - isArchieve: { type: Boolean, default: false }, + isArchive: { type: Boolean, default: false }, lastPasswordReset: { type: Number }, visitorBrowserID: { type: String }, }); -const userModel = (db: any) => { - return MainModel(db, "User", signupschema, "User"); +const AuthModel = (db: any) => { + return MainModel(db, "UserAuth", AuthSchema, "UserAuth"); }; -export default userModel; +export default AuthModel; diff --git a/src/shared/V1Models/Project/share-model.ts b/src/shared/V1Models/Project/share-model.ts new file mode 100644 index 0000000..316be17 --- /dev/null +++ b/src/shared/V1Models/Project/share-model.ts @@ -0,0 +1,52 @@ +import mongoose, { Document, Schema } from "mongoose"; +import MainModel from "../../connect/mongoose.ts"; + +export interface IShare extends Document { + Createdby: { + userId: mongoose.Types.ObjectId; + Email: string; + }; + isActive: boolean; + Share_People: [ + { + userId: mongoose.Types.ObjectId; + Email: string; + AccessPoint: string; + } + ]; + Description: string; + createdAt: number; + projectId: mongoose.Types.ObjectId; +} + +const shareSchema: Schema = new Schema({ + CreatedBy: { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + Email: { type: String, ref: "User", required: true }, + }, + isActive: { type: Boolean, default: false }, + Share_People: [ + { + userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, + Email: { type: String, ref: "User", required: true }, + AccessPoint: { + type: String, + default: "Can view", + enum: ["Can view", "Can edit", "Can comment"], + }, + }, + ], + createdAt: { + type: Number, + default: Date.now(), + }, + projectId: { type: mongoose.Schema.Types.ObjectId, ref: "Scene" }, +}); +const shareModel = (db: any) => { + return MainModel(db, "Shared", shareSchema, "Shared"); +}; +export default shareModel; diff --git a/src/shared/middleware/rbacMiddleware.ts b/src/shared/middleware/rbacMiddleware.ts new file mode 100644 index 0000000..3a7497a --- /dev/null +++ b/src/shared/middleware/rbacMiddleware.ts @@ -0,0 +1,13 @@ +import { Response, Request, NextFunction } from "express"; +import { AuthenticatedRequest } from "../../shared/utils/token.ts"; +type Role = "Admin" | "User"; +const authorizedRoles = (...allowedRoles: Role[]) => { + return (req: AuthenticatedRequest, res: Response, next: NextFunction) => { + if (!req.user || !allowedRoles.includes(req.user.role as Role)) { + res.status(403).json({ message: "Access Denied" }); + return; + } + next(); + }; +}; +export default authorizedRoles; diff --git a/src/shared/redis/redis.ts b/src/shared/redis/redis.ts new file mode 100644 index 0000000..cda43ab --- /dev/null +++ b/src/shared/redis/redis.ts @@ -0,0 +1,21 @@ +import Redis from "ioredis"; +import * as dotenv from "dotenv"; +dotenv.config(); +const redis = new Redis.default({ + host: + process.env.REDIS_ENV === "true" + ? process.env.REDIS_DOCKER + : process.env.REDIS_LOCAL, + port: parseInt(process.env.REDIS_PORT || "6379"), + password: "", + db: 0, +}); +redis.on("connect", () => { + console.log(`Connected to Redis to ${redis.options.port}`); +}); + +redis.on("error", (err: unknown) => { + console.error("Redis connection error:", err); +}); + +export default redis; diff --git a/src/shared/security/token.ts b/src/shared/security/token.ts deleted file mode 100644 index ea71280..0000000 --- a/src/shared/security/token.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import * as Jwt from 'jsonwebtoken'; // Correct way to import jsonwebtoken - -// Define a new interface extending Request -interface AuthenticatedRequest extends Request { - user?: { - email: string; - // Add more fields as needed based on your JWT payload - }; -} -const tokenGenerator = (email: string) => { - const token = Jwt.sign({ email: email }, "Milestone", { - expiresIn: "3hours", - }); - return token; - }; - -const tokenValidator = (req: AuthenticatedRequest, res: Response, next: NextFunction): void => { - const token: string | undefined = req.headers.token as string | undefined; - if (!token) { - res.status(403).json({ - msg: "No token present", - }); - return; // Make sure to return after sending a response - } - - try { - const decoded = Jwt.verify(token,"Milestone") as { email: string }; // adjust if your JWT payload has more fields - req.user = decoded; - next(); - } catch (err) { - res.status(401).json({ - msg: "Invalid Token", - }); - } -}; - -export { tokenValidator,tokenGenerator }; diff --git a/src/shared/services/auth/authServices.ts b/src/shared/services/auth/authServices.ts new file mode 100644 index 0000000..f6b3717 --- /dev/null +++ b/src/shared/services/auth/authServices.ts @@ -0,0 +1,466 @@ +import AuthModel from "../../V1Models/Auth/userAuthModel.ts"; +import UsersDataModel from "../../V1Models/Auth/user.ts"; +import nodemailer from "nodemailer"; +import tokenType from "../../V1Models/Auth/tokenModel.ts"; +import Jwt from "jsonwebtoken"; + +import { hashValidator, hashGenerate } from "../../utils/Hasing.ts"; +import redis from "../../redis/redis.ts"; +import { tokenGenerator, tokenRefreshGenerator } from "../../utils/token.ts"; +interface Iserviceuser { + userName: string; + Email: string; + Password: string; + profilePicture: string; +} +interface IloginUser { + Email: string; + Password: string; + fingerprint: string; +} +interface IresetToken { + resetToken: string; + newPassword: string; + confirmPassword: string; +} +interface IUser { + Email: string; + userName: string; + _id: string; +} +const jwt_secret = process.env.JWT_SECRET as string; +export function extractOrg(Email: string) { + return Email.split("@")[1].split(".")[0]; +} +async function findExistingUserEmail(Email: string) { + const organization = extractOrg(Email); + const existingUser = await AuthModel(organization).findOne({ + Email: Email, + isArchive: false, + }); + return existingUser; +} + +export const AuthSignup = async ( + data: Iserviceuser +): Promise<{ + status: string; +}> => { + const { userName, Email, Password, profilePicture } = data; + try { + let role; + const caseChange = Email.toLowerCase(); + const organization = extractOrg(caseChange); + const Existing_User = await findExistingUserEmail(caseChange); + if (Existing_User) { + return { status: "User already exists" }; + } else { + const hashPassword = await hashGenerate(Password); + const userCount = await AuthModel(organization).countDocuments({}); + role = userCount === 0 ? "Admin" : "User"; + const isShare = "true"; + const newuser = await AuthModel(organization).create({ + userName: userName, + Email: caseChange, + Password: hashPassword, + }); + const UserDatas = await UsersDataModel(organization).create({ + userId: newuser._id, + role: role, + isShare: isShare, + profilePicture: profilePicture, + }); + return { status: "Success" }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; + +export const AuthLogin = async ( + data: IloginUser +): Promise<{ status: string; data?: Object }> => { + try { + const { Email, Password, fingerprint } = data; + const caseChange = Email.toLowerCase(); + const organization = extractOrg(caseChange); + const Existing_User = await findExistingUserEmail(caseChange); + if (!Existing_User) return { status: "User Not Found!!! Kindly signup..." }; + else { + const existingMail = await getUserFromCacheOrDB(caseChange); + const checkPassword = await hashValidator( + Password, + existingMail.Password + ); + if (!checkPassword) + return { + status: "Email & Password is invalid...Check the credentials", + }; + + const browserID = existingMail.visitorBrowserID; + if (browserID && browserID !== fingerprint) { + await Promise.all([ + redis.del(`token:${caseChange}`), + redis.del(`user:${caseChange}`), + ]); + await AuthModel(organization).updateOne( + { Email: caseChange }, + { visitorBrowserID: "" } + ); + await tokenType(organization).updateOne( + { userId: Existing_User._id }, + { refreshToken: "" } + ); + return { + status: "Already LoggedIn on another browser....Please logout!!!", + }; + } + const UserData = await UsersDataModel(organization).findOne({ + userId: existingMail._id, + isArchive: false, + }); + if (!UserData) + return { + status: "User_Datas not found", + }; + const redisTokenRaw = await redis.get(`token:${caseChange}`); + if (redisTokenRaw) { + const cachedTokens = JSON.parse(redisTokenRaw); + try { + Jwt.verify(cachedTokens.token, jwt_secret); + return { + status: "Success", + data: { + message: "login successfull", + email: existingMail.Email, + name: existingMail.userName, + userId: existingMail._id, + isShare: UserData.isShare, + token: cachedTokens.token, + refreshToken: cachedTokens.refreshToken, + }, + }; + } catch (err) { + console.log("Access token expired. Generating new..."); + } + } + const tokenValidation = tokenGenerator( + existingMail.Email, + UserData.role, + existingMail._id, + organization + ); + const refreshTokenvalidation = tokenRefreshGenerator( + existingMail.Email, + UserData.role, + existingMail._id, + organization + ); + + await handleTokenCache( + existingMail._id.toString(), + existingMail.Email, + tokenValidation, + refreshTokenvalidation + ); + + const updatedUser = await AuthModel(organization) + .findByIdAndUpdate( + existingMail._id, + { visitorBrowserID: fingerprint }, + { new: true } + ) + .select("-__v -Profilepicture"); + if (!updatedUser) + return { + status: "User update failed.", + }; + await redis.setex( + `user:${existingMail.Email}`, + 3600, + JSON.stringify(updatedUser) + ); + const finalResult = { + message: "login successfull", + email: existingMail.Email, + name: existingMail.userName, + userId: existingMail._id, + isShare: UserData.isShare, + // updatedUser: updatedUser as IUser, + token: tokenValidation, + refreshToken: refreshTokenvalidation, + }; + return { + status: "Success", + data: finalResult, + }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const AuthLogout = async ({ + Email, +}: Iserviceuser): Promise<{ status: string }> => { + try { + const caseChange = Email.toLowerCase(); + const organization = extractOrg(caseChange); + const Existing_User = await findExistingUserEmail(caseChange); + if (!Existing_User) return { status: "User not found" }; + + const tokenData = await tokenType(organization).findOne({ + userId: Existing_User._id, + isArchive: false, + }); + + if (!tokenData) return { status: "Token not found" }; + await Promise.all([ + redis.del(`token:${caseChange}`), + redis.del(`user:${caseChange}`), + ]); + tokenData.refreshToken = ""; + await tokenData.save(); + Existing_User.visitorBrowserID = ""; + await Existing_User.save(); + return { status: "Success" }; + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const forgetPassword = async ({ + Email, +}: Iserviceuser): Promise<{ status: string }> => { + try { + const caseChange = Email.toLowerCase(); + const organization = extractOrg(caseChange); + const Existing_User = await findExistingUserEmail(caseChange); + if (Existing_User) { + if (Existing_User.lastPasswordReset) { + const lastPasswordReset = Existing_User.lastPasswordReset; + const now = Date.now(); + const timeDiff = now - lastPasswordReset; + const diffInHours = Math.floor(timeDiff / (1000 * 60 * 60)); + if (diffInHours < 24) + return { + status: "You can only reset your password once every 24 hours.", + }; + } + const transport = nodemailer.createTransport({ + service: "gmail", + secure: true, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }); + const resetToken = tokenGenerator( + Email, + Existing_User.Role as string, + Existing_User._id as string, + organization + ); + const userTokenData = await tokenType(organization).findOne({ + Email: Email, + isArchive: false, + }); + if (!userTokenData) { + await tokenType(organization).create({ + Email: Existing_User.Email, + userId: Existing_User._id, + resetToken: resetToken, + resetTokenExpiry: Date.now(), + }); + } else { + userTokenData.resetToken = resetToken; + userTokenData.resetTokenExpiry = new Date(); + await userTokenData.save(); + } + const Receiver = { + from: process.env.EMAIL_USER, + to: Email, + subject: "Password Reset Request", + text: `Click the below link to generate the new password \n ${ + process.env.CLIENT_URL + }/reset-password/${tokenGenerator( + Email, + Existing_User.Role as string, + Existing_User._id as string, + organization + )}`, + }; + await transport.sendMail(Receiver); + + return { status: "Success" }; + } else { + return { status: "Email not found" }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const resetPassword = async ({ + resetToken, + newPassword, + confirmPassword, +}: IresetToken): Promise<{ status: string }> => { + try { + const decoded = Jwt.verify(resetToken, "Milestone"); + if (typeof decoded === "string" || !("email" in decoded)) + return { status: "Invalid token payload." }; + + const Email = decoded.email; + const organization = extractOrg(Email); + if (newPassword !== confirmPassword) return { status: "Password mismatch" }; + + const userData = await AuthModel(organization).findOne({ + Email: Email, + isArchive: false, + }); + const userTokenData = await tokenType(organization).findOne({ + Email: Email, + isArchive: false, + }); + if (!userData || !userTokenData) return { status: "User not found" }; + else { + const tokenexpiry = userTokenData.resetTokenExpiry as Date; + const now = Date.now(); + const tokenAge = tokenexpiry ? now - tokenexpiry.getTime() : 0; + const TOKEN_EXPIRATION_TIME = 15 * 60 * 1000; + + if (!tokenexpiry || tokenAge > TOKEN_EXPIRATION_TIME) + return { status: "Token is invalid or expired." }; + const hashPassword = await hashGenerate(newPassword); + const lastPasswordReset = Date.now(); + await AuthModel(organization).findByIdAndUpdate( + userData._id, + { Password: hashPassword, lastPasswordReset: lastPasswordReset }, + { new: true } + ); + userTokenData.resetToken = ""; + userTokenData.resetTokenExpiry = undefined; + await userTokenData.save(); + await ResetFromCacheOrDB(Email); + return { status: "Success" }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; + +async function getUserFromCacheOrDB(Email: string) { + const redisUserKey = `user:${Email}`; + try { + const cachedUser = await redis.get(redisUserKey); + if (cachedUser) return JSON.parse(cachedUser); + + const Existing_User = await findExistingUserEmail(Email); + + if (Existing_User) { + await redis.setex(redisUserKey, 3600, JSON.stringify(Existing_User)); + } + return Existing_User; + } catch (error) { + return error; + } +} + +async function ResetFromCacheOrDB(Email: string) { + const redisUserKey = `user:${Email}`; + try { + const organization = extractOrg(Email); + const cachedUser = await redis.get(redisUserKey); + if (cachedUser) { + const user = await AuthModel(organization) + .findOne({ + Email: Email, + isArchive: false, + }) + .lean() + .select("-__v -Profilepicture"); + + if (user) { + await redis.setex(redisUserKey, 3600, JSON.stringify(user)); + } + return user; + } + } catch (error) { + return error; + } +} + +async function handleTokenCache( + userId: string, + Email: string, + token: string, + refreshToken: string +) { + const redisTokenKey = `token:${Email}`; + try { + const organization = extractOrg(Email); + const tokenPayload = { + token, + refreshToken, + userId, + Email, + }; + await redis.setex(redisTokenKey, 3600, JSON.stringify(tokenPayload)); + + let tokenDoc = await tokenType(organization).findOne({ + userId, + isArchive: false, + }); + if (!tokenDoc) { + tokenDoc = await tokenType(organization).create({ userId, refreshToken }); + } else { + await tokenType(organization).findByIdAndUpdate( + tokenDoc._id, + { refreshToken }, + { new: true } + ); + } + + return tokenPayload; + } catch (error) { + return error; + } +} diff --git a/src/shared/services/builder/cameraService.ts b/src/shared/services/builder/cameraService.ts new file mode 100644 index 0000000..94c1d7c --- /dev/null +++ b/src/shared/services/builder/cameraService.ts @@ -0,0 +1,155 @@ +import projectModel from "../../model/project/project-model.ts"; +import userModel from "../../model/user-Model.ts"; +import versionModel from "../../model/version/versionModel.ts"; +import UsersDataModel from "../../V1Models/Auth/user.ts"; +import cameraModel from "../../V1Models/Builder/cameraModel.ts"; +import { existingProjectById } from "../helpers/ProjecthelperFn.ts"; +interface IcameraData { + userId: string; + position: Object; + target: Object; + rotation: Object; + organization: string; + projectId: string; + versionId: string; +} +interface IgetCameras { + organization: string; + userId?: string; +} +export const SetCamera = async ( + data: IcameraData +): Promise<{ status: string; data?: Object }> => { + try { + const { + userId, + position, + target, + rotation, + organization, + projectId, + versionId, + } = data; + const LivingProject = await existingProjectById( + projectId, + organization, + userId + ); + if (!LivingProject) return { status: "Project not found" }; + const existingCamera = await cameraModel(organization).findOne({ + userId: userId, + }); + if (existingCamera) { + const updateCamera = await cameraModel(organization).findOneAndUpdate( + { + userId: userId, + projectId: projectId, + versionId: versionId, + isArchive: false, + }, + { position: position, target: target, rotation: rotation }, + { new: true } + ); + return { + status: "Update Success", + data: updateCamera, + }; + } else { + const newCamera = await cameraModel(organization).create({ + userId, + projectId, + versionId, + position, + target, + rotation, + }); + + return { + status: "Creation Success", + data: newCamera, + }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const GetCamers = async ( + data: IgetCameras +): Promise<{ status: string; data?: Object }> => { + const { userId, organization } = data; + try { + const findCamera = await cameraModel(organization).findOne({ + userId: userId, + }); + if (!findCamera) { + return { status: "user not found" }; + } else { + return { status: "Success", data: findCamera }; + } + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const onlineActiveDatas = async ( + data: IgetCameras +): Promise<{ status: string; data?: Object }> => { + const { organization } = data; + try { + const findactiveUsers = await UsersDataModel(organization).find({ + activeStatus: "online", + }); + + const cameraDataPromises = findactiveUsers.map(async (activeUser: any) => { + const cameraData = await cameraModel(organization) + .findOne({ userId: activeUser._id }) + .select("position target rotation -_id"); + + if (cameraData) { + return { + position: cameraData.position, + target: cameraData.target, + rotation: cameraData.rotation, + userData: { + _id: activeUser._id, + userName: activeUser.userName, + email: activeUser.email, + activeStatus: activeUser.activeStatus, + }, + }; + } + return null; + }); + + const cameraDatas = (await Promise.all(cameraDataPromises)).filter( + (singledata: any) => singledata !== null + ); + + return { status: "Success", data: cameraDatas }; + } catch (error: unknown) { + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; diff --git a/src/shared/services/helpers/ProjecthelperFn.ts b/src/shared/services/helpers/ProjecthelperFn.ts index 3f01f19..3ce8cc3 100644 --- a/src/shared/services/helpers/ProjecthelperFn.ts +++ b/src/shared/services/helpers/ProjecthelperFn.ts @@ -2,6 +2,7 @@ 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 AuthModel from "../../V1Models/Auth/userAuthModel.ts"; export const existingProject = async ( projectUuid: string, organization: string, @@ -20,7 +21,7 @@ export const existingUser = async (userId: string, organization: string) => { console.log("Invalid ObjectId format"); return null; } - const userData = await userModel(organization).findOne({ + const userData = await AuthModel(organization).findOne({ _id: userId, }); return userData; @@ -76,3 +77,15 @@ export const generateUntitledProjectName = async ( return newNumber === 0 ? "Untitled" : `Untitled ${newNumber}`; }; +export const existingProjectById = async ( + projectId: string, + organization: string, + userId: string +) => { + const projectData = await projectModel(organization).findOne({ + _id: projectId, + createdBy: userId, + isArchive: false, + }); + return projectData; +}; diff --git a/src/shared/services/home/homeService.ts b/src/shared/services/home/homeService.ts index 04c26fd..758d174 100644 --- a/src/shared/services/home/homeService.ts +++ b/src/shared/services/home/homeService.ts @@ -1,10 +1,12 @@ import projectModel from "../../model/project/project-model.ts"; import userModel from "../../model/user-Model.ts"; +import UsersDataModel from "../../V1Models/Auth/user.ts"; import { existingUser } from "../helpers/ProjecthelperFn.ts"; interface IRecentData { organization: string; userId: string; + role: string; } interface IProject { _id: string; @@ -19,26 +21,31 @@ interface searchProjectInterface { userId: string; organization: string; } +interface RoleFilter { + isArchive: boolean; + createdBy?: string; +} export const RecentlyAdded = async (data: IRecentData) => { try { - const { userId, organization } = data; + const { userId, organization, role } = data; const userExisting = await existingUser(userId, organization); if (!userExisting) return { status: "User not found" }; - const userRecentData = await userModel(organization) - .findOne({ _id: userId }) + const userRecentData = await UsersDataModel(organization) + .findOne({ userId: userId, isArchive: false }) .populate({ path: "recentlyViewed", model: projectModel(organization), select: "_id", }); + let filter = { isArchive: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } const populatedProjects = userRecentData.recentlyViewed as IProject[]; const RecentDatas = await Promise.all( populatedProjects.map(async (project) => { const projectExisting = await projectModel(organization) - .findOne({ - _id: project._id, - isArchive: false, - }) + .findOne(filter) .select("_id projectName createdBy thumbnail createdAt isViewed"); return projectExisting; }) @@ -57,26 +64,29 @@ export const searchProject = async (data: searchProjectInterface) => { if (!userExisting) return { status: "User not found" }; const findprojectName = await projectModel(organization).find({ projectName: { $regex: `${searchName}`, $options: "i" }, // 'i' makes it case-insensitive - isArchive: false, - }) - if (!findprojectName||findprojectName.length===0) return { status: "Project not found" } + isArchive: false, + }); + if (!findprojectName || findprojectName.length === 0) + return { status: "Project not found" }; return { status: "Success", data: findprojectName }; } catch (error: unknown) { return { status: error }; } -} +}; export const searchTrashProject = async (data: searchProjectInterface) => { try { const { userId, organization, searchName } = data; const userExisting = await existingUser(userId, organization); if (!userExisting) return { status: "User not found" }; const findprojectName = await projectModel(organization).find({ - projectName: { $regex: `${searchName}`, $options: "i" }, - isArchive: true,isDeleted:false - }) - if (!findprojectName||findprojectName.length===0) return { status: "Project not found" } + projectName: { $regex: `${searchName}`, $options: "i" }, + isArchive: true, + isDeleted: false, + }); + if (!findprojectName || findprojectName.length === 0) + return { status: "Project not found" }; return { status: "Success", data: findprojectName }; } catch (error: unknown) { return { status: error }; } -} \ No newline at end of file +}; diff --git a/src/shared/services/project/project-Services.ts b/src/shared/services/project/project-Services.ts index 69d5a6c..17300cb 100644 --- a/src/shared/services/project/project-Services.ts +++ b/src/shared/services/project/project-Services.ts @@ -1,6 +1,8 @@ +import { ObjectId } from "mongoose"; import projectModel from "../../model/project/project-model.ts"; import userModel from "../../model/user-Model.ts"; import versionModel from "../../model/version/versionModel.ts"; +import { AuthenticatedRequest } from "../../utils/token.ts"; import { existingProject, existingUser, @@ -8,6 +10,7 @@ import { previousVersion, generateUntitledProjectName, } from "../helpers/ProjecthelperFn.ts"; +import UsersDataModel from "../../V1Models/Auth/user.ts"; interface CreateProjectInput { projectName: string; projectUuid: string; @@ -23,26 +26,32 @@ interface updateProjectInput { thumbnail?: string; sharedUsers?: string[]; organization: string; + role: string; } interface GetProjectsInterface { userId: string; organization: string; + role: string; } interface ProjectInterface { projectId: string; userId: string; organization: string; + role: string; +} +interface RoleFilter { + isArchive: boolean; + createdBy?: string; } - export const createProject = async (data: CreateProjectInput) => { try { - const { userId, thumbnail, sharedUsers, organization, projectUuid } = data; + const { thumbnail, sharedUsers, projectUuid, userId, organization } = data; const userExisting = await existingUser(userId, organization); if (!userExisting) { return { status: "user_not_found", }; - } + } const projectExisting = await existingProject( projectUuid, organization, @@ -93,13 +102,15 @@ export const createProject = async (data: CreateProjectInput) => { export const GetAllProjects = async (data: GetProjectsInterface) => { try { - const { userId, organization } = data; + const { userId, organization, role } = data; await existingUser(userId, organization); if (!existingUser) return { status: "User not found" }; + let filter = { isArchive: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } const projectDatas = await projectModel(organization) - .find({ - isArchive: false, - }) + .find(filter) .select("_id projectName createdBy thumbnail createdAt projectUuid"); if (projectDatas) return { status: "Success", Datas: projectDatas }; } catch (error: unknown) { @@ -109,41 +120,42 @@ export const GetAllProjects = async (data: GetProjectsInterface) => { export const DeleteProject = async (data: ProjectInterface) => { try { - const { projectId, organization, userId } = data; + const { projectId, organization, userId, role } = data; const ExistingUser = await existingUser(userId, organization); if (!ExistingUser) return { status: "User not found" }; - const existingProject = await projectModel(organization).findOne({ - _id: projectId, - createdBy: userId, - isArchive: false, - }); + let filter = { _id: projectId, isArchive: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } + const existingProject = await projectModel(organization).findOne(filter); if (!existingProject) return { status: "Project not found" }; const updateProject = await projectModel(organization).findOneAndUpdate( - { _id: projectId, isArchive: false }, + filter, { isArchive: true, DeletedAt: new Date() }, { new: true } ); - if (updateProject) return { status: "Success",project: updateProject }; + if (updateProject) return { status: "Success", project: updateProject }; } catch (error: unknown) { return { status: error }; } }; export const updateProject = async (data: updateProjectInput) => { try { - const { projectId, organization, userId, projectName, thumbnail } = data; + const { projectId, organization, userId, projectName, thumbnail, role } = + data; const ExistingUser = await existingUser(userId, organization); if (!ExistingUser) return { status: "User not found" }; - const existingProject = await projectModel(organization).findOne({ - _id: projectId, - createdBy: userId, - isArchive: false, - }); + let filter = { _id: projectId, isArchive: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } + const existingProject = await projectModel(organization).findOne(filter); if (!existingProject) return { status: "Project not found" }; if (projectName !== undefined) projectName; if (thumbnail !== undefined) thumbnail; const updateProject = await projectModel(organization) .findOneAndUpdate( - { _id: projectId, isArchive: false }, + filter, { projectName: projectName, thumbnail: thumbnail }, { new: true } ) @@ -156,18 +168,23 @@ export const updateProject = async (data: updateProjectInput) => { const maxLength: number = 6; export const viewProject = async (data: ProjectInterface) => { try { - const { projectId, organization, userId } = data; + const { projectId, organization, userId, role } = data; const userExisting = await existingUser(userId, organization); if (!userExisting) return { status: "User not found" }; - const existingProject = await projectModel(organization).findOne({ - _id: projectId, - createdBy: userId, + const RecentUserDoc = await UsersDataModel(organization).findOne({ + userId: userId, isArchive: false, }); + let filter = { _id: projectId, isArchive: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } + const existingProject = await projectModel(organization).findOne(filter); if (!existingProject) return { status: "Project not found" }; - const newArr = userExisting?.recentlyViewed || []; - if (userExisting?.recentlyViewed.length === 0) { + const newArr = RecentUserDoc?.recentlyViewed || []; + if (RecentUserDoc?.recentlyViewed.length === 0) { newArr.push(projectId); + await RecentUserDoc.save(); } else { const index = newArr.indexOf(projectId); if (index !== -1) { @@ -179,21 +196,13 @@ export const viewProject = async (data: ProjectInterface) => { newArr.pop(); } } - await userModel(organization).updateOne( + await UsersDataModel(organization).updateOne( { _id: userId }, { recentlyViewed: newArr }, { new: true } ); const projectData = await projectModel(organization) - .findOneAndUpdate( - { - _id: projectId, - createdBy: userId, - isArchive: false, - }, - { isViewed: Date.now() }, - { new: true } - ) + .findOneAndUpdate(filter, { isViewed: Date.now() }, { new: true }) .select("_id projectName createdBy thumbnail createdAt"); return { status: "Success", data: projectData }; } catch (error: unknown) { diff --git a/src/shared/services/trash/trashService.ts b/src/shared/services/trash/trashService.ts index 2976f95..ae833ff 100644 --- a/src/shared/services/trash/trashService.ts +++ b/src/shared/services/trash/trashService.ts @@ -1,19 +1,27 @@ import projectModel from "../../model/project/project-model.ts"; interface IOrg { organization: string; + role: string; + userId: string; } interface IRestore { projectId: string; organization: string; + role: string; + userId: string; +} +interface RoleFilter { + isArchive: boolean; + createdBy?: string; } export const TrashDatas = async (data: IOrg) => { try { - const { organization } = data; - - const TrashLists = await projectModel(organization).find({ - isArchive: true, - isDeleted: false, - }); + const { organization, role, userId } = data; + let filter = { isArchive: true, isDeleted: false } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } + const TrashLists = await projectModel(organization).find(filter); if (!TrashLists) return { staus: "Trash is Empty" }; const TrashDocs: any[] = []; for (const Trash of TrashLists) { @@ -47,14 +55,15 @@ export const TrashDatas = async (data: IOrg) => { }; export const RestoreTrashData = async (data: IRestore) => { try { - const { projectId, organization } = data; - const findProject = await projectModel(organization).findOne({ - _id: projectId, - isArchive: true, - }); + const { projectId, organization, role, userId } = data; + let filter = { isArchive: true, _id: projectId } as RoleFilter; + if (role === "User") { + filter.createdBy = userId; + } + const findProject = await projectModel(organization).findOne(filter); if (!findProject) return { status: "Project not found" }; const restoreData = await projectModel(organization).findOneAndUpdate( - { _id: projectId, isArchive: true }, + filter, { isArchive: false, DeletedAt: null }, { new: true } ); diff --git a/src/shared/services/version/versionService.ts b/src/shared/services/version/versionService.ts new file mode 100644 index 0000000..fbd0bb2 --- /dev/null +++ b/src/shared/services/version/versionService.ts @@ -0,0 +1,127 @@ +import projectModel from "../../model/project/project-model.ts"; +import assetModel from "../../V1Models/Builder/assetModel.ts"; +import versionModel from "../../model/version/versionModel.ts"; + +class VersionService { + async getCurrentVersion(db: string, projectId: string) { + const project = await projectModel(db).findById(projectId); + if (!project) throw new Error("Project not found"); + + return { + versionNumber: parseFloat(project.Present_version || "0.0"), + versionString: project.Present_version || "0.0", + }; + } + + async createNewVersion( + db: string, + projectId: string, + userId: string, + description?: string + ) { + const project = await projectModel(db).findById(projectId); + if (!project) throw new Error("Project not found"); + + const { versionNumber } = await this.getCurrentVersion(db, projectId); + const newVersion = parseFloat((versionNumber + 0.1).toFixed(1)); + const versionName = `Version ${newVersion.toFixed(1)}`; + + const version = await versionModel(db).create({ + versionName, + version: newVersion, + projectId: project._id, + createdBy: userId, + description, + }); + + await projectModel(db).findByIdAndUpdate(projectId, { + Present_version: newVersion.toFixed(1), + total_versions: newVersion.toFixed(1), + }); + + return version; + } + + async saveCurrentStateAsVersion( + db: string, + projectId: string, + userId: string, + description?: string + ) { + // Create new version + const newVersion = await this.createNewVersion( + db, + projectId, + userId, + description + ); + + // Get all assets from previous version + const previousVersion = parseFloat((newVersion.version - 0.1).toFixed(1)); + const previousVersionDoc = await versionModel(db).findOne({ + projectId, + version: previousVersion, + }); + console.log('previousVersionDoc: ', previousVersionDoc); + + let previousAssets = []; + if (previousVersionDoc) { + previousAssets = await assetModel(db).find({ + projectId, + versionId: previousVersionDoc._id, + isArchive: false, + }); + } + + // Copy assets to new version + const newAssets = await Promise.all( + previousAssets.map(async (asset) => { + console.log('previousAssets: ', previousAssets); + const newAsset = { ...asset.toObject(), versionId: newVersion._id }; + delete newAsset._id; + return await assetModel(db).create(newAsset); + }) + ); + + return { + version: newVersion, + assets: newAssets, + }; + } + + async getVersionHistory(db: string, projectId: string) { + const versions = await versionModel(db) + .find({ projectId, isArchive: false }) + .sort({ version: 1 }) + .populate("createdBy", "name email"); + + const versionHistory = await Promise.all( + versions.map(async (version) => { + const assets = await assetModel(db).find({ + projectId, + versionId: version._id, + isArchive: false, + }); + + const assetCounts = assets.reduce((acc, asset) => { + acc[asset.type] = (acc[asset.type] || 0) + 1; + return acc; + }, {}); + + return { + version: version.version.toFixed(1), + versionName: version.versionName, + createdAt: version.createdAt, + createdBy: version.createdBy, + description: version.description, + assets: assetCounts, + totalAssets: assets.length, + }; + }) + ); + + return versionHistory; + } +} + +export default new VersionService(); diff --git a/src/shared/security/Hasing.ts b/src/shared/utils/Hasing.ts similarity index 62% rename from src/shared/security/Hasing.ts rename to src/shared/utils/Hasing.ts index 7b52ee1..601228d 100644 --- a/src/shared/security/Hasing.ts +++ b/src/shared/utils/Hasing.ts @@ -1,24 +1,25 @@ -import bcrypt from 'bcryptjs'; - -const saltRounds = 10; -export const hashGenerate = async (Password:string) => { - try { - const salt = await bcrypt.genSalt(saltRounds); - const hash = await bcrypt.hash(Password, salt); - - return hash; - } catch (error) { - return error; - } -}; -export const hashValidator = async (password:string, hashedPassword:string) => { - - try { - const result = await bcrypt.compare(password, hashedPassword); - - return result; - } catch (error) { - return false; - } -}; -; +import bcrypt from "bcryptjs"; + +const saltRounds = 10; +export const hashGenerate = async (Password: string) => { + try { + const salt = await bcrypt.genSalt(saltRounds); + const hash = await bcrypt.hash(Password, salt); + + return hash; + } catch (error) { + return error; + } +}; +export const hashValidator = async ( + password: string, + hashedPassword: string +) => { + try { + const result = await bcrypt.compare(password, hashedPassword); + + return result; + } catch (error) { + return false; + } +}; diff --git a/src/shared/security/mongosecurity.ts b/src/shared/utils/mongosecurity.ts similarity index 97% rename from src/shared/security/mongosecurity.ts rename to src/shared/utils/mongosecurity.ts index 6cb1364..1e665b1 100644 --- a/src/shared/security/mongosecurity.ts +++ b/src/shared/utils/mongosecurity.ts @@ -1,34 +1,34 @@ -import { MongoClient } from 'mongodb' -export default async function mongoAdminCreation() { - const uri = process.env.MONGO_URI!; // Replace with your MongoDB URI - const client = new MongoClient(uri); - const user = { - user:"admin", - pwd: process.env.MONGO_PASSWORD!, - roles: [{ role: "root", db: process.env.MONGO_AUTH_DB || "admin" }], - }; - try { - await client.connect(); - const db = client.db('admin'); // Specify the actual database where the user should be created - - // Check if the user already exists - const userExists = await db.collection('system.users').findOne({ user: user.user}); - - if (userExists) { - console.log(`User ${user} already exists`); - return; // Exit if the user already exists - } - - - - // Create the user - await db.command({ createUser: user.user, pwd: user.pwd, roles: user.roles }); - console.log("User created successfully!") - } catch (error) { - console.error("Error creating user:",error); - } finally { - await client.close(); - } - } - +import { MongoClient } from 'mongodb' +export default async function mongoAdminCreation() { + const uri = process.env.MONGO_URI!; // Replace with your MongoDB URI + const client = new MongoClient(uri); + const user = { + user:"admin", + pwd: process.env.MONGO_PASSWORD!, + roles: [{ role: "root", db: process.env.MONGO_AUTH_DB || "admin" }], + }; + try { + await client.connect(); + const db = client.db('admin'); // Specify the actual database where the user should be created + + // Check if the user already exists + const userExists = await db.collection('system.users').findOne({ user: user.user}); + + if (userExists) { + console.log(`User ${user} already exists`); + return; // Exit if the user already exists + } + + + + // Create the user + await db.command({ createUser: user.user, pwd: user.pwd, roles: user.roles }); + console.log("User created successfully!") + } catch (error) { + console.error("Error creating user:",error); + } finally { + await client.close(); + } + } + // mongoAdminCreation \ No newline at end of file diff --git a/src/shared/utils/token.ts b/src/shared/utils/token.ts new file mode 100644 index 0000000..552b29b --- /dev/null +++ b/src/shared/utils/token.ts @@ -0,0 +1,143 @@ +import { Request, Response, NextFunction } from "express"; +import Jwt from "jsonwebtoken"; +import dotenv from "dotenv"; +import { extractOrg } from "../services/auth/authServices.ts"; +import AuthModel from "../V1Models/Auth/userAuthModel.ts"; +dotenv.config(); + +export interface AuthenticatedRequest extends Request { + user?: { + Email: string; + role: string; + userId: string; + organization: string; + }; +} +const jwt_secret = process.env.JWT_SECRET as string; +const refresh_jwt_secret = process.env.REFRESH_JWT_SECRET as string; +const tokenGenerator = ( + Email: string, + role: string, + userId: string, + organization: string +) => { + const token = Jwt.sign( + { Email: Email, role: role, userId, organization: organization }, + jwt_secret, + { + expiresIn: "3h", + } + ); + return token; +}; +const tokenRefreshGenerator = ( + Email: string, + role: string, + userId: string, + organization: string +) => { + const token = Jwt.sign( + { Email: Email, role: role, userId, organization: organization }, + refresh_jwt_secret, + { + expiresIn: "30d", + } + ); + return token; +}; +const tokenValidator = async ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction +): Promise => { + const token: string | undefined = req.headers.token as string | undefined; + const refresh_token = req.headers["refresh_token"] as string | undefined; + if (!token) { + res.status(403).json({ + msg: "No token present", + }); + return; + } + + try { + const decoded = Jwt.verify(token, jwt_secret) as { + Email: string; + role: string; + userId: string; + organization: string; + }; + if (!decoded) { + res.status(403).json({ + success: false, + status: 403, + message: "Invalid Token", + }); + return; + } + req.user = decoded; + next(); + } catch (err) { + // res.status(401).json({ + // msg: "Invalid Token", + // }); + if (!refresh_token) { + res.status(403).json({ + success: false, + status: 403, + message: "No refresh token present", + }); + return; + } + try { + const decodedRefresh = Jwt.verify(refresh_token, refresh_jwt_secret) as { + Email: string; + role: string; + userId: string; + organization: string; + }; + if (!decodedRefresh) { + res.status(403).json({ + success: false, + status: 403, + message: "Invalid Token", + }); + return; + } + const newAccessToken = tokenGenerator( + decodedRefresh.Email, + decodedRefresh.role, + decodedRefresh.userId, + decodedRefresh.organization + ); + res.setHeader("x-access-token", newAccessToken); + req.user = decodedRefresh; + return next(); + } catch (err) { + const decodedAny = Jwt.decode(token || refresh_token) as { + Email?: string; + role: string; + userId: string; + organization: string; + }; + if (decodedAny?.Email) { + const organization = extractOrg(decodedAny?.Email); + const user = await AuthModel(organization).findOne({ + Email: decodedAny.Email, + isArchieve: false, + }); + if (user) { + user.visitorBrowserID = ""; + await user.save(); + } + } + res.status(403).json({ + success: false, + status: 403, + message: "Invalid Token", + }); + return; + } + } +}; + +export { tokenValidator, tokenGenerator, tokenRefreshGenerator };