diff --git a/.DS_Store b/.DS_Store index 5a26913..102d287 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/controllers/location.controller.js b/controllers/location.controller.js new file mode 100644 index 0000000..2935c0a --- /dev/null +++ b/controllers/location.controller.js @@ -0,0 +1,25 @@ +import { + createLocation, + getLocationByQuery, + getLocationByRadius, +} from "../models/Location/operations"; + +export const createLocationController = async (req, res) => { + try { + const { user } = res.locals; + console.log("BG LOCATIONREQUEST BODY", req.body, user); + // const location = await createLocation({ ...req.body, user: user?.id }); + res.status(200).json({ location: "location" }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + +export const getLocationController = async (req, res) => { + try { + const location = await getLocationByQuery(req.query); + res.status(200).json(location); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; diff --git a/controllers/user.controller.js b/controllers/user.controller.js index 4015fd9..2a30c9c 100644 --- a/controllers/user.controller.js +++ b/controllers/user.controller.js @@ -4,6 +4,7 @@ import dayjs from "dayjs"; import { getAccessToken, getRefreshToken, + getEncryptedPayload, } from "../middlewares/jwt.middleware.js"; export const createUserController = async (req, res, next) => { @@ -45,14 +46,20 @@ export const loginController = async (req, res, next) => { } const accessToken = getAccessToken({ id: userExists._id }); + const encryptedKey = getEncryptedPayload({ id: userExists._id }); const refreshToken = getRefreshToken(); userExists.refreshToken = refreshToken; await userExists.save(); - res - .status(200) - .json({ result: { accessToken, refreshToken, name: userExists?.name } }); + res.status(200).json({ + result: { + accessToken, + refreshToken, + encryptedKey, + name: userExists?.name, + }, + }); } catch (error) { console.log("loginController Error", error); next(error); @@ -73,4 +80,3 @@ export const renewAccessTokenController = async (req, res, next) => { next(error); } }; - diff --git a/documentation/documentation.json b/documentation/documentation.json index 98c819f..384265f 100644 --- a/documentation/documentation.json +++ b/documentation/documentation.json @@ -196,6 +196,54 @@ } } }, + "/location/create": { + "post": { + "tags": [ + "Location" + ], + "summary": "Create New Location", + "parameters": [ + { + "in": "query", + "name": "key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "required": true + }, + "coordinates": { + "type": "array", + "required": true + }, + "venue": { + "type": "string", + "required": false + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": {} + } + } + } + }, "/uploads/single/{folder}": { "post": { "tags": [ diff --git a/documentation/index.js b/documentation/index.js index fb4c366..2b840dd 100644 --- a/documentation/index.js +++ b/documentation/index.js @@ -4,6 +4,7 @@ import upload from "./upload.js"; import user from "./user.js"; import profile from "./profile.js"; import admin from "./admin.js"; +import location from "./location.js" const GENERAL_CONFIG = { openapi: "3.0.1", @@ -43,6 +44,7 @@ const GENERAL_CONFIG = { let paths = { ...user, ...profile, + ...location, ...upload, ...admin, }; diff --git a/documentation/location.js b/documentation/location.js new file mode 100644 index 0000000..89112e5 --- /dev/null +++ b/documentation/location.js @@ -0,0 +1,49 @@ +export default { + "/location/create": { + post: { + tags: ["Location"], + summary: "Create New Location", + parameters: [ + { + in: "query", + name: "key", + required: true, + schema: { + type: "string", + }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + properties: { + name: { + type: "string", + required: true, + }, + coordinates: { + type: "array", + required: true, + }, + venue: { + type: "string", + required: false, + }, + + }, + }, + }, + }, + }, + responses: { + 200: { + description: "OK", + content: {}, + }, + }, + }, + }, +}; diff --git a/helpers/geoJson.helper.js b/helpers/geoJson.helper.js new file mode 100644 index 0000000..8b09836 --- /dev/null +++ b/helpers/geoJson.helper.js @@ -0,0 +1,13 @@ +import { Schema } from "mongoose"; + +export default new Schema({ + type: { type: String, default: "Point", enum: ["Point"] }, + coordinates: [Number], +}); + +export const coordinatesToGeoJson = (coords) => { + return { + type: "Point", + coordinates: [coords.latitude, coords.longitude], + }; +}; diff --git a/helpers/index.js b/helpers/index.js index e69de29..0f164f3 100644 --- a/helpers/index.js +++ b/helpers/index.js @@ -0,0 +1,2 @@ +export { default as locationSchema } from "./geoJson.helper"; +export * from "./geoJson.helper"; diff --git a/index.js b/index.js index 4d06cc4..1137db9 100644 --- a/index.js +++ b/index.js @@ -11,7 +11,7 @@ import swaggerUi from "swagger-ui-express"; import swaggerDoc from "./documentation/documentation.json"; import getSwaggerDocument from "./documentation"; // import socketState from "./state/socketState.js"; -import { AdminRoutes, UserRoutes } from "./routes"; +import { AdminRoutes, UserRoutes, LocationRoutes } from "./routes"; dotenv.config(); @@ -43,6 +43,7 @@ app.use(express.json()); app.use("/v0/docs", swaggerUi.serve, swaggerUi.setup(swaggerDoc)); app.use("/v0/user", UserRoutes); app.use("/v0/admin", AdminRoutes); +app.use("/v0/location", LocationRoutes); app.use((err, req, res, next) => { const statusCode = err.status || 500; diff --git a/middlewares/jwt.middleware.js b/middlewares/jwt.middleware.js index 2b5f1e1..4bf5cd7 100644 --- a/middlewares/jwt.middleware.js +++ b/middlewares/jwt.middleware.js @@ -2,14 +2,42 @@ import jwt from "jsonwebtoken"; import crypto from "node:crypto"; import User from "../models/User/model.js"; import Access from "../models/Access/model.js"; +import dotenv from "dotenv"; + +dotenv.config(); + +const { ECT_KEY, ECT_IV, ECT_METHOD } = process.env; export const getAccessToken = (payload) => { - return jwt.sign(payload, process.env.ECT_KEY, { + return jwt.sign(payload, ECT_KEY, { algorithm: "HS256", expiresIn: "7m", }); }; +export const getEncryptedPayload = (payload) => { + const cipher = crypto.createCipheriv(ECT_METHOD, ECT_KEY, ECT_IV); + let encrypted = cipher.update(JSON.stringify(payload), "utf8", "hex"); + encrypted += cipher.final("hex"); + const tag = cipher.getAuthTag().toString("hex"); + return `${ECT_IV}:${tag}:${encrypted}`; +}; + +export const getDecryptedPayload = (payload) => { + const [ivHex, authTagHex, encryptedHex] = payload.split(":"); + + const iv = Buffer.from(ivHex, "hex"); + const authTag = Buffer.from(authTagHex, "hex"); + const decipher = crypto.createDecipheriv(ECT_METHOD, ECT_KEY, ECT_IV); + + decipher.setAuthTag(authTag); // Verify integrity + + let decrypted = decipher.update(encryptedHex, "hex", "utf8"); + decrypted += decipher.final("utf8"); + + return JSON.parse(decrypted); +}; + export const getRefreshToken = () => { return crypto.randomBytes(32).toString("hex"); }; @@ -68,3 +96,31 @@ export const authorize = (requiredModule, requiredPermission) => { } }; }; + +export const authorizeWithEncryptedKey = ( + requiredModule, + requiredPermission, +) => { + return async (req, res, next) => { + try { + const { key } = req.query; + + if (!key) { + throw { code: 401, message: "Encrypted Auth Key Missing" }; + } + + const verifiedPayload = getDecryptedPayload(key); + + const userExists = await User.exists({ _id: verifiedPayload.id }); + if (!userExists) { + throw { code: 404, message: "User not found" }; + } + + res.locals.user = verifiedPayload; + next(); + } catch (error) { + console.error("AUTH_ERROR", error); + res.status(error.code || 401).json(error); + } + }; +}; diff --git a/models/Branch/model.js b/models/Branch/model.js index b7028c5..6fd0d91 100644 --- a/models/Branch/model.js +++ b/models/Branch/model.js @@ -1,16 +1,5 @@ import { model, Schema, Types } from "mongoose"; -const coordinatesSchema = new Schema({ - latitude: { - type: Number, - required: true, - }, - longitude: { - type: Number, - required: true, - }, -}); - const schema = new Schema({ name: { type: String, @@ -41,10 +30,6 @@ const schema = new Schema({ type: Date, required: true, }, - coordinates: { - type: coordinatesSchema, - required: true, - }, type: { type: String, required: true, diff --git a/models/Location/model.js b/models/Location/model.js index 344432e..75eb94f 100644 --- a/models/Location/model.js +++ b/models/Location/model.js @@ -1,5 +1,24 @@ import { model, Schema, Types } from "mongoose"; +import { locationSchema } from "../../helpers"; -const schema = new Schema({}); +const schema = new Schema({ + user: { + type: Types.ObjectId, + required: true, + ref: "user", + }, + coordinates: locationSchema, + createdOn: { + type: Date, + required: true, + }, + venue: { + type: Types.ObjectId, + required: false, + ref: "venue", + }, +}); + +schema.index({ coordinates: "2dsphere" }); export default model("location", schema); diff --git a/models/Location/operations.js b/models/Location/operations.js index b24e22b..32b7281 100644 --- a/models/Location/operations.js +++ b/models/Location/operations.js @@ -1,8 +1,14 @@ import Model from "./model.js"; - +import { coordinatesToGeoJson } from "../../helpers/geoJson.helper.js"; +import dayjs from "dayjs"; // Create export const createLocation = async (data) => { - return await Model.create(data); + const geoJson = coordinatesToGeoJson(data.coordinates); + return await Model.create({ + ...data, + coordinates: geoJson, + createdOn: dayjs().toISOString(), + }); }; // Read (Get by ID) @@ -13,7 +19,12 @@ export const getLocationById = async (id) => { // Read (Get by Query) export const getLocationByQuery = async (query) => { return await Model.findOne(query); -} +}; + +// Read (Get by Radius) +export const getLocationByRadius = async (radius) => { + return await Model.find(query); +}; // Read (Get all) export const getAllLocations = async () => { diff --git a/models/Partner/model.js b/models/Partner/model.js index caa535f..6be23dd 100644 --- a/models/Partner/model.js +++ b/models/Partner/model.js @@ -1,5 +1,67 @@ import { model, Schema, Types } from "mongoose"; -const schema = new Schema({}); +const contactPersonSchema = new Schema({ + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + mobile: { + type: String, + required: true, + }, + designation: { + type: String, + required: true, + }, +}); + +const schema = new Schema({ + name: { + type: String, + required: true, + }, + createdBy: { + type: Types.ObjectId, + required: true, + ref: "user", + }, + createdOn: { + type: Date, + required: true, + }, + email: { + type: String, + required: true, + }, + mobile: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + city: { + type: String, + required: true, + }, + state: { + type: String, + required: true, + }, + pincode: { + type: String, + required: true, + }, + isActive: { + type: Boolean, + default: true, + }, + contactPersons: [contactPersonSchema], +}); export default model("partner", schema); diff --git a/models/Vendor/model.js b/models/Vendor/model.js index f069ac1..e3c2dcf 100644 --- a/models/Vendor/model.js +++ b/models/Vendor/model.js @@ -1,5 +1,67 @@ import { model, Schema, Types } from "mongoose"; -const schema = new Schema({}); +const contactPersonSchema = new Schema({ + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + }, + mobile: { + type: String, + required: true, + }, + designation: { + type: String, + required: true, + }, +}); + +const schema = new Schema({ + name: { + type: String, + required: true, + }, + createdBy: { + type: Types.ObjectId, + required: true, + ref: "user", + }, + createdOn: { + type: Date, + required: true, + }, + email: { + type: String, + required: false, + }, + mobile: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + city: { + type: String, + required: true, + }, + state: { + type: String, + required: true, + }, + pincode: { + type: String, + required: true, + }, + isActive: { + type: Boolean, + default: true, + }, + contactPersons: [contactPersonSchema], +}); export default model("vendor", schema); diff --git a/models/Venue/model.js b/models/Venue/model.js new file mode 100644 index 0000000..1800106 --- /dev/null +++ b/models/Venue/model.js @@ -0,0 +1,46 @@ +import { model, Schema, Types } from "mongoose"; +import { locationSchema } from "../../helpers"; + +const schema = new Schema({ + name: { + type: String, + required: true, + }, + createdBy: { + type: Types.ObjectId, + required: true, + ref: "user", + }, + createdOn: { + type: Date, + required: true, + }, + location: locationSchema, + category: { + type: String, + required: true, + default: "OWN", + enum: ["OWN", "PARTNER", "VENDOR", "CLIENT", "DISTRIBUTOR"], + }, + type: { + type: String, + required: true, + enum: [ + "HQ", + "BRANCH", + "WAREHOUSE", + "VENDOR", + "PARTNER", + "DISTRIBUTOR", + "CLIENT", + ], + }, + entity: { + type: Types.ObjectId, + required: true, + }, +}); + +schema.index({ location: "2dsphere" }); + +export default model("venue", schema); diff --git a/models/Venue/operations.js b/models/Venue/operations.js new file mode 100644 index 0000000..c763d24 --- /dev/null +++ b/models/Venue/operations.js @@ -0,0 +1,31 @@ +import Model from "./model.js"; + +// Create +export const createVenue = async (data) => { + return await Model.create(data); +}; + +// Read (Get by ID) +export const getVenueById = async (id) => { + return await Model.findById(id); +}; + +// Read (Get by Query) +export const getVenueByQuery = async (query) => { + return await Model.findOne(query); +} + +// Read (Get all) +export const getAllVenues = async () => { + return await Model.find(); +}; + +// Update +export const updateVenue = async (id, data) => { + return await Model.findByIdAndUpdate(id, data, { new: true }); +}; + +// Aggregate +export const aggregateVenue = async (pipeline) => { + return await Model.aggregate(pipeline); +}; diff --git a/routes/index.js b/routes/index.js index 6eec26a..ca17bde 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,3 +1,4 @@ export { default as UserRoutes } from "./user.route"; export { default as ProfileRoutes } from "./profile.route"; export { default as AdminRoutes } from "./admin.route"; +export { default as LocationRoutes } from "./location.route"; diff --git a/routes/location.route.js b/routes/location.route.js new file mode 100644 index 0000000..593dfff --- /dev/null +++ b/routes/location.route.js @@ -0,0 +1,13 @@ +import { Router } from "express"; +import { authorizeWithEncryptedKey } from "../middlewares/jwt.middleware"; +import { createLocationController } from "../controllers/location.controller"; + +const router = new Router(); + +router.post( + "/create", + authorizeWithEncryptedKey("location", "write"), + createLocationController, +); + +export default router;