location update optimized with schema changes and geoJson Compatability testing endpoint with encrypted key authorization

This commit is contained in:
Shibi Chakkaravarthy
2026-04-17 14:15:11 +05:30
parent b44abf9c88
commit 92769cfd38
19 changed files with 459 additions and 27 deletions
Vendored
BIN
View File
Binary file not shown.
+25
View File
@@ -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 });
}
};
+10 -4
View File
@@ -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);
}
};
+48
View File
@@ -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": [
+2
View File
@@ -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,
};
+49
View File
@@ -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: {},
},
},
},
},
};
+13
View File
@@ -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],
};
};
+2
View File
@@ -0,0 +1,2 @@
export { default as locationSchema } from "./geoJson.helper";
export * from "./geoJson.helper";
+2 -1
View File
@@ -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;
+57 -1
View File
@@ -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);
}
};
};
-15
View File
@@ -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,
+20 -1
View File
@@ -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);
+14 -3
View File
@@ -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 () => {
+63 -1
View File
@@ -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);
+63 -1
View File
@@ -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);
+46
View File
@@ -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);
+31
View File
@@ -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);
};
+1
View File
@@ -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";
+13
View File
@@ -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;