From e2582f7464d3ad7d7b6c91210637cfc13d0f36d8 Mon Sep 17 00:00:00 2001 From: Josepablo Cruz Date: Mon, 30 Mar 2026 16:52:38 -0600 Subject: [PATCH] feat: Adding groups feature without privacity enabled --- v1/README.md | 7 ++ v1/src/apps/private/groups/routes.js | 9 ++ v1/src/apps/private/groups/services.js | 116 +++++++++++++++++++++ v1/src/apps/private/index.js | 15 +-- v1/src/lib/Models/company_groups.models.js | 12 +++ v1/src/lib/Models/index.js | 6 ++ v1/src/lib/Models/load_templates.model.js | 67 ++++++++++++ 7 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 v1/src/apps/private/groups/routes.js create mode 100644 v1/src/apps/private/groups/services.js create mode 100644 v1/src/lib/Models/company_groups.models.js create mode 100644 v1/src/lib/Models/load_templates.model.js diff --git a/v1/README.md b/v1/README.md index 2e1b1cb..061fa28 100644 --- a/v1/README.md +++ b/v1/README.md @@ -237,6 +237,7 @@ The following list of endpoints requires a JWT. - `GET /loads`: List loads related to my company. - `GET /load-attachments`: List load attachments related to my company or load id. + - `GET /groups/private`: Get the private group for my company. ### /public-load-tracking @@ -350,6 +351,12 @@ This endpoint is part of /loads endpoint - `GET /users/:companyId` : Get the list of users within a company. - `GET /:id` : Get data from specific company. +### /groups + + - `GET /private` : Get the private group list from my company. If it does not exist, it will be created. + - `PATCH /private/:id` : Add a company (company id) to the private group list. + - `DELETE /private/:id` : Remove a company (company id) from the private group list. + ### /load-attachments - `POST /loading/:id` : Upload/Update a loading attachment. diff --git a/v1/src/apps/private/groups/routes.js b/v1/src/apps/private/groups/routes.js new file mode 100644 index 0000000..ae29577 --- /dev/null +++ b/v1/src/apps/private/groups/routes.js @@ -0,0 +1,9 @@ +'use strict'; +const router = require('express').Router(); +const services= require('./services.js'); + +router.get('/private', services.getPrivateGroup); +router.patch('/private/:id', services.addCompanyToGroup); +router.delete('/private/:id', services.removeCompanyFromGroup); + +module.exports = router; diff --git a/v1/src/apps/private/groups/services.js b/v1/src/apps/private/groups/services.js new file mode 100644 index 0000000..076e282 --- /dev/null +++ b/v1/src/apps/private/groups/services.js @@ -0,0 +1,116 @@ +"use strict"; +const { getModel } = require( '../../../lib/Models' ); +const { getPagination } = require( '../../../lib/Misc.js' ); + +const CompanyModel = getModel('companies'); +const CompanyGroupModel = getModel('company_groups'); + +const company_projection = 'company_name rfc categories products truck_type company_city company_state'; + +async function getOrCreatePrivateGroup( owner ) { + let result = await CompanyGroupModel + .findOne({ owner: owner }) + .populate({ + path: 'allowedCompanies', + select: company_projection, + populate: { + path: 'categories', + select: '-_id name' + } + }); + + if( result ){ + return result; + } + + result = new CompanyGroupModel({ + owner: owner, + list_name: "private" + }); + await result.save(); + + return await CompanyGroupModel + .findOne({ owner: owner }) + .populate({ + path: 'allowedCompanies', + select: company_projection, + populate: { + path: 'categories', + select: '-_id name' + } + }); +} + +async function getPrivateGroup( req , res ) { + try{ + const companyId = req.context.companyId; + const result = await getOrCreatePrivateGroup( companyId ); + return res.send( result ); + }catch( error ){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +} + +async function addCompanyToGroup(req, res){ + try{ + const companyId = req.context.companyId; + const newEntryId = req.params.id; + + if( newEntryId === companyId ){ + return res.status(400).send({ error: "can't add yourself" }); + } + + const company = await CompanyModel.findById( newEntryId ); + if( !company ){ + return res.status(400).send({ error: "invalid entry id" }); + } + + let result = await getOrCreatePrivateGroup( companyId ); + + /// Add to the list only if it is not there already + if( ! result.allowedCompanies.some( entry => entry.id === newEntryId ) ){ + await CompanyGroupModel.findByIdAndUpdate(result.id, { + $addToSet: { allowedCompanies: company.id } + }); + } + + result = await getOrCreatePrivateGroup( companyId ); + return res.status(200).send( result ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +} + +async function removeCompanyFromGroup(req, res){ + try{ + const companyId = req.context.companyId; + const newEntryId = req.params.id; + + if( newEntryId === companyId ){ + return res.status(400).send({ error: "you are not at the list" }); + } + + let result = await getOrCreatePrivateGroup( companyId ); + + /// If the item belongs to the list remove it + if( result.allowedCompanies.some( entry => entry.id === newEntryId ) ){ + await CompanyGroupModel.findByIdAndUpdate(result.id, { + $pull: { allowedCompanies: newEntryId } + }); + } + + result = await getOrCreatePrivateGroup( companyId ); + return res.status(200).send( result ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +} + +module.exports = { + getPrivateGroup, + addCompanyToGroup, + removeCompanyFromGroup +}; diff --git a/v1/src/apps/private/index.js b/v1/src/apps/private/index.js index 1f62546..fbcb19c 100644 --- a/v1/src/apps/private/index.js +++ b/v1/src/apps/private/index.js @@ -17,6 +17,7 @@ const proposals = require('./proposals/routes.js'); const users = require('./users/routes.js'); const vehicles = require('./vehicles/routes.js'); const notifications = require('./notifications/routes.js'); +const company_groups = require('./groups/routes.js'); router.use( jwtValidator.middleware ); router.use( context.middleware ); @@ -25,6 +26,7 @@ router.use('/account', account); router.use('/budgets', budgets); router.use('/branches', branches); router.use('/companies', companies); +router.use('/groups', company_groups); router.use('/load-attachments', loadAttachments ); router.use('/loads', loads); router.use('/loads_driver', loads_driver); @@ -33,17 +35,4 @@ router.use('/proposals', proposals); router.use('/users', users); router.use('/vehicles', vehicles); -/* -router.use('/orders', test); -router.use('/mailer', test); -router.use('/memberships', test); -router.use('/bootresolvers', test); -router.use('/news', test); -router.use('/branches', test); -router.use('/trackings', test); -router.use('/upload', test); -router.use('/calendars', test); -router.use('/dashboard', test); -*/ - module.exports = router; diff --git a/v1/src/lib/Models/company_groups.models.js b/v1/src/lib/Models/company_groups.models.js new file mode 100644 index 0000000..d4296b1 --- /dev/null +++ b/v1/src/lib/Models/company_groups.models.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +const schema = new Schema({ + owner: { type: Schema.Types.ObjectId, ref: 'companies', required: true }, /// Company owner of this list + list_name: { type: String }, /// Name of the list + allowedCompanies: [{ type: Schema.Types.ObjectId, ref: 'companies' }], /// Behaves as a tuple, not repeated elements + createdAt: { type : Date, required : true, default : () => { return Date.now(); } }, + updatedAt: { type : Date, required : true, default : () => { return Date.now(); } } +}); + +module.exports = mongoose.model( "companygroups", schema ); diff --git a/v1/src/lib/Models/index.js b/v1/src/lib/Models/index.js index 5feef98..76345d8 100644 --- a/v1/src/lib/Models/index.js +++ b/v1/src/lib/Models/index.js @@ -4,9 +4,11 @@ const branches = require('./branches.model.js'); const budgets = require('./budgets.model.js'); const cities = require('./cities.model.js'); const companies = require('./companies.model.js'); +const companygroups = require('./company_groups.models.js') const countries = require('./countries.model.js'); const load_attachments = require('./load-attachments.model.js'); const loads = require('./loads.model.js'); +const loadtemplates = require('./load_templates.model.js'); const mailer = require('./mailer.model.js'); const memberships = require('./memberships.model.js'); const meta_data = require('./meta-data.model.js'); @@ -34,12 +36,16 @@ function getModel( name ){ return cities; case 'companies': return companies; + case 'company_groups': + return companygroups; case 'countries': return countries; case 'load_attachments': return load_attachments; case 'loads': return loads; + case 'load_templates': + return loadtemplates; case 'mailer': return mailer; case 'memberships': diff --git a/v1/src/lib/Models/load_templates.model.js b/v1/src/lib/Models/load_templates.model.js new file mode 100644 index 0000000..7e91b87 --- /dev/null +++ b/v1/src/lib/Models/load_templates.model.js @@ -0,0 +1,67 @@ +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +const address = new Schema({ + company_name: { type: String }, + + street_address1: { type: String }, + street_address2: { type: String }, + city: { type: String }, + state: { type: String }, + country: { type: String }, + zipcode: { type: String }, + + landmark: { type: String }, + + lat: { type: String }, + lng: { type: String }, +}); + + +const pointSchema = new Schema({ + type: { + type: String, + enum: ['Point'], + required: true + }, + coordinates: { + type: [Number], + required: true + } +}); + + +const schema = new Schema({ + company: { type: Schema.Types.ObjectId, ref: 'companies', required: true }, + + alert_list: [{ type: String, lowercase: true }], + + origin_warehouse : { type: Schema.Types.ObjectId, ref: 'branches' }, + destination_warehouse : { type: Schema.Types.ObjectId, ref: 'branches' }, + + origin: address, + origin_geo: { + type: pointSchema, + }, + destination: address, + destination_geo: { + type: pointSchema, + }, + + categories: [{ type: Schema.Types.ObjectId, ref: 'productcategories' }], + product: { type: Schema.Types.ObjectId, ref: 'products' }, + + truck_type: { type: String }, + tyre_type: { type: String }, + weight: { type: Number }, + estimated_cost: { type: Number }, + + distance: { type: Number }, + actual_cost: { type: Number }, + notes: { type: String }, + + created_by: { type: Schema.Types.ObjectId, ref: 'users' }, // shipper + createdAt: { type : Date, required : true, default : () => { return Date.now(); } } +}); + +module.exports = mongoose.model( "loadtemplates", schema );