feat: Split v1 and v2 apis

This commit is contained in:
Josepablo C
2024-08-05 15:33:23 -06:00
parent 49ee7d7b5a
commit c3f0b08cb7
149 changed files with 284 additions and 33 deletions

19
v1/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
# Use an official Python runtime as a parent image
FROM node:18-alpine
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY src /app/src
COPY package.json /app
COPY dotenv /app/.env
RUN apk add bash bash-completion vim
RUN npm install --include=dev
EXPOSE 3000
ENV ROOT_PATH="/app"
ENTRYPOINT npm start

444
v1/README.md Normal file
View File

@@ -0,0 +1,444 @@
# ETA API
ETA Viaporte API
# Dependencies
- NodeJS v18
- Docker
# Endpoints
All endpoints that return a list of elements is paginable with the following queries:
- `elements`: Number of elements for each page.
- `page`: Page number (from 0 to n)
Example:
- `/endpoint?elements=50&page=2` : Get page 2 with 50 elements per page.
- `/endpoint?page=2` : Get page 2 with default (10) elements per page.
- `/endpoint?elements=50` : Get page 0 with 50 elements.
## Public endpoints
Read registered resources:
- `GET /account`: At this location you can: register, login, recover password, renew JWT.
- `GET /countries`: List registered countries.
- `GET /cities`: List registered cities.
- `GET /meta-data`: List registered meta-data.
- `GET /meta-groups`: List registered meta-data.
- `GET /product-categories`: List registered product-categories.
- `GET /public-companies`: List registered companies. Pagination limited to 100 elements and page 0.
- `GET /public-loads`: List registered loads.
- `GET /public-load-attachments/download/:id`: Downloads the attachment of the load identified by Id.
- `GET /public-vehicles`: List registered vehicles.
- `GET /states`: List registered states.
- `POST /contact-email`: Send an email from the Landing Page.
All these endpoints support the following parameters (except for those with `public-` as prefix, for example `public-vehicles`):
- `/` : List registered resources with pagination.
- `/:id` : Read specific resource identified by Id.
- `/find?regex=xxx` : List resources that matches with regex (support pagination).
### /account
This endpoint provides mechanisms to register, login, recover password and renew JWT.
The __Login__ and __Renew__ process will return 2 tokens, the `accessToken` (JWT) which is used in every further request to authorize access to private endpoints. And the 2nd is the `session_token` (renew token), which should be used after the expiration of the JWT, in order to generate another _JWT_ and _session token_ without the need of use the _email_ and _password__.
The _session token_ expiration is typically 30 days after its generation. Every renewal replaces the token in the DB and the expiration is reset again to 30 days.
#### POST /account/authorize
Login process, returns a JWT and Renew Token
Expects a body with the following data:
```{.json}
{
"email":"testing@etaviaporte.com",
"password":"PasswordExample"
}
```
Returns:
```{.javascript}
{
"accessToken" : "JWT",
"payload" : "Content in the JWT",
"session_token": "Renew token",
"session_token_exp": "Expiration of renew token in UNIX epoch format",
"user" : { ... }
}
```
#### GET /account/authorize/:session_token
Renewal of JWT with session token, will return the new session token and a new JWT only if the session token is not expired, otherwise the request will fail and the login process should be used instead.
Example of usage:
```
GET /account/authorize/"HERE_GOES_THE_SESSION_TOKEN"
```
Returns:
```{.json}
{
"accessToken" : "JWT",
"payload" : "Content in the JWT",
"session_token": "Renew token",
"session_token_exp": "Expiration of renew token in UNIX epoch format",
"user" : { ... }
}
```
#### POST /account/signup
Create a new user. This will trigger an email with the OTP (one time password) to verify the email. There is no expiration time, but it is expected that the Fron End removes the checksum from the local storage after an expiration time defined in the Front End.
This will return a checksum string to be used in the confirmation process.
Expects a body with the following data:
```{.json}
{
"email":"testing@etaviaporte.com",
"password":"PasswordExample"
}
```
Returns:
```{.javascript}
{
"checksum" : "JWT"
}
```
#### PATCH /account/signup
Confirms registration of new user. This will trigger a welcome email to the user.
There is no timeout to confirm the email, but it is expected that the Fron End removes the checksum from the local storage after an expiration time defined in the Front End.
If the checksum matches but the user is already registered, then this request will be rejected.
Expects a body with the same data as the POST request, but adding the OTP received in the email, the company type decided by the user and the checksum generated by the POST request. Here is an example:
```{.json}
{
"email":"testing@etaviaporte.com",
"password":"PasswordExample",
"otp":"OTP string",
"checksum":"Checksum generated in the POST request"
}
```
Returns:
```{.json}
{
"msg" : "User created successfully!"
}
```
#### POST /account/recover
Reset password request. This will trigger an email with the OTP (one time password) to verify the email. There is no expiration time, but it is expected that the Fron End removes the checksum from the local storage after an expiration time defined in the Front End.
This will return a checksum string to be used in the confirmation process.
Expects a body with the following data:
```{.json}
{
"email":"testing@etaviaporte.com",
"password":"new password"
}
```
Returns:
```{.javascript}
{
"checksum" : "JWT"
}
```
#### PATCH /account/recover
Confirms the email to recover the password.
There is no timeout to confirm the email, but it is expected that the Fron End removes the checksum from the local storage after an expiration time defined in the Front End.
Expects a body with the same data as the POST request, but adding the OTP received in the email, and the checksum generated by the POST request. Here is an example:
```{.json}
{
"email":"testing@etaviaporte.com",
"password":"New Password Example",
"otp":"OTP string",
"checksum":"Checksum generated in the POST request"
}
```
Returns:
```{.json}
{
"msg" : "Password is reset!"
}
```
### POST /contact-email
Send an email from the Landing Page:
Body content:
```{.json}
{
"email":"josepablo134@gmail.com",
"name":"Josepablo C.",
"message":"This is a test"
}
```
Returns:
```{.json}
{
"msg": "Email sent!"
}
```
### GET /public-companies
Get public fields from registered companies.
- `GET /shipper`: List registered shippers that are not hidden only.
- `GET /carrier`: List registered carriers that are not hidden only.
### GET /public-loads
Get public fields from registered loads.
- `GET /`: List only loads with status Published.
### GET /public-vehicles
Get public fields from registered vehicles.
- `GET /published`: List only latest published vehicles.
- `GET /location`: List only location from vehicles in status Free.
### /news
- `GET /` : Get a list of elements with pagination.
- `GET /find` : Find a list of elements with any of the following fields:
- regex
- `GET /:id` : Get data from specific element.
- `GET /download/:image_name`: Download image from DB.
## Private Endpoints
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.
### /public-load-tracking
- `GET /:id`: Get tracking data from load id
### Test Endpoint
A private endpoint to test the JWT and the api response.
- `POST /apitest`: Return whatever is sent on the body, queries and parameters.
- `GET /version`: Return the API version.
### /account
Complete the register process
- `POST /register` : Creates the company from the body data provided and assigns account to this new company.
#### POST /account/register
Send company data to complete the register.
After this process, the logged user will receive the flag "isVerified" and the company id will be associated, at the same time, the role will be set to "owner" and the permissions will be set according to the company type: `role_shipper` or `role_carrier`.
This request can only happen once, since this process is the creation of a new company in the DB.
After setting up the company, the updates can be handled in the `/companies` endpoint.
Body content:
```{.json}
{
"company_type":"carrier|shipper",
"company_name" : "Company Name",
"company_description" : "This is a description example"
}
```
Returns a company object
```{.json}
{
"_id": "company_id",
"company_type":"carrier|shipper",
"company_name" : "Company Name",
"company_description" : "This is a description example"
}
```
### /branches
- `GET /find` : Find a list of elements with any of the following fields:
- categories
- branch_name
- phone
- city
- state
- truck_type
- `POST /new` : Creates a new element.
- `PATCH /:id` : Updates data from specific element.
- `DELETE /:id` : Delete element from company. Returns the element data.
- `GET /:id` : Get data from specific element.
### /budgets
- `GET /find` : Find a list of elements with any of the following fields:
- client
- material
- origin
- destination
- truck_type
- num_tons
- price_per_ton
- tonnage
- pickup_distance
- delivery_distance
- warehouse_distance
- total_km_travel
- cost_per_liter
- fuel_price_per_liter
- other_fuel_expenses
- total_fuel_consumed
- total_cost_fuel
- driver_salary
- accomadation_allowance
- other_administrative_expenses
- total_before_tax
- total_utility_per_km
- total_profit
- profit_percentage
- total_administrative_expenses
- `POST /new` : Creates a new element.
- `PATCH /:id` : Updates data from specific element.
- `DELETE /:id` : Delete element from company. Returns the element data.
- `GET /:id` : Get data from specific element.
### /calendars
This endpoint is part of /loads endpoint
### /companies
- `GET /own` : Get own company data.
- `PATCH /own` : Update own company data.
- `GET /shipper` : Get list of shipper companies with pagination using the following filters: company_type, company_name, truck_type, categories, company_state, company_city.
- $sort[ field ] : -1/1 ; Sort result by field name
- `GET /carrier` : Get list of carrier companies with pagination using the following filters: company_type, company_name, truck_type, categories, company_state, company_city
- $sort[ field ] : -1/1 ; Sort result by field name
- `GET /users/:companyId` : Get the list of users within a company.
- `GET /:id` : Get data from specific company.
### /load-attachments
- `POST /loading/:id` : Upload/Update a loading attachment.
- `POST /downloading/:id` : Upload/Update a download attachment
- `GET /load/:id` : Get the list of attachment ids from load.
- `GET /:id` : Get attachment file.
- `GET /` : Get attachment list from company.
### /loads
- `GET /find` : Find a list of elements with any of the following fields:
- companyId
- carrier
- vehicle
- driver
- status
- posted_by_name
- load_status
- published_date
- loaded_date
- transit_date
- categories
- product
- shipment_code
- company_name[$regex] : Regex string to find company_name
- company_name[$options] : Regex options from MongoDB filter description
- $sort[ field ] : -1/1 ; Sort result by field name
- `GET /calendar` : Find a list of elements with any of the following fields:
- date[gte] : Date grater than.
- date[lte] : Date less than.
- load_status : string enumerator ['Published', 'Loading', 'Transit', 'Downloading', 'Delivered'].
- global : 1 To return calendar of all company, or 0/undefined for personal calendar only.
- $sort[ field ] : -1/1 ; Sort result by field name
- `POST /new` : Creates a new element.
- `PATCH /:id` : Updates data from specific element.
- `DELETE /:id` : Delete element from company. Returns the element data.
- `GET /:id` : Get data from specific element.
##### Example of filter and sort:
- `GET /calendar?$sort[createdAt]=-1&date[gte]=2022-08-25&date[lte]=2022-05-25&load_status=Loading`: Get loads within date range 2022-08-25 -> 2022-05-25 with load_state "Loading" and sort it by createdAt field.
- `GET /loads/find?$sort[createdAt]=-1&load_status=Loading`: Get loads with status "Loading" sorted by createdAt.
### /proposals
- `GET /find` : Find a list of elements with any of the following fields:
- load
- categories
- branch_name
- phone, city
- state
- truck_type
- `POST /new` : Creates a new element.
- `PATCH /:id` : Updates data from specific element.
- `DELETE /:id` : Delete element from company. Returns the element data.
- `GET /:id` : Get data from specific element.
### /upload
Work In Progress
### /users
- `GET /find` : Look for a user within ETA.
- `POST /member` : Create a new user within the company (only for owner or manager users).
- `PATCH /member/:id` : Update member data (except permissions, company, job_role and password).
- `DELETE /member/:id` : Delete member but keep metadata for future references. The user will be disabled and won't be able to log in again.
- `GET /profile` : Read profile data.
- `PATCH /profile` : Update profile data (except permissions, company, job_role and password).
- `GET /:id` : Get user information with its ID.
### /vehicles
__This endpoint is only valid for carriers.__
- `GET /find` : Find a list of elements with any of the following fields:
- categories,
- active_load,
- load_shipper,
- driver,
- vehicle_code,
- vehicle_name,
- vehicle_number,
- circulation_serial_number,
- truck_type,
- tyre_type,
- city,
- state,
- status,
- destino
- `POST /new` : Creates a new element.
- `PATCH /:id` : Updates data from specific element.
- `DELETE /:id` : Delete element from company. Returns the element data.
- `GET /:id` : Get data from specific element.

9
v1/dotenv Normal file
View File

@@ -0,0 +1,9 @@
SERVER_PORT=3000
ROOT_PATH=/mnt/d/Projects/WebDevWorkspace/ETAViaporte/etaapi/v1
API_CONFIG=src/config/apiConfig_local.json
############################
# PATHS relative to ROOT
############################
LIB_PATH=src/lib
MODELS_PATH=src/lib/Models
HANDLERS_PATH=src/lib/Handlers

53
v1/package.json Normal file
View File

@@ -0,0 +1,53 @@
{
"name": "etaapi",
"version": "1.0.0",
"description": "ETA API",
"main": "index.js",
"scripts": {
"start": "node src/",
"dev": "nodemon src/",
"test": "mocha test/lib/handlers/proposals"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.com/jcruzbaasworkspace/enruta/etaapi.git"
},
"author": "Josepablo C.",
"license": "ISC",
"bugs": {
"url": "https://gitlab.com/jcruzbaasworkspace/enruta/etaapi/issues"
},
"homepage": "https://gitlab.com/jcruzbaasworkspace/enruta/etaapi#readme",
"dependencies": {
"@aws-sdk/client-s3": "^3.427.0",
"audit": "^0.0.6",
"axios": "^1.5.1",
"body-parser": "^1.20.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-fileupload": "^1.4.1",
"express-jwt": "^8.4.1",
"form-data": "^4.0.0",
"helmet": "^7.0.0",
"jsonschema": "^1.4.1",
"jsonwebtoken": "^9.0.2",
"knex": "^2.5.1",
"mongodb-core": "^3.2.7",
"mongoose": "^7.5.4",
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"nodemailer": "^6.9.5",
"nodemailer-sendgrid": "^1.0.3",
"nodemon": "^3.0.1",
"objection": "^3.1.4",
"uuid": "^9.0.1"
},
"devDependencies": {
"chai": "^5.1.0",
"mocha": "^10.3.0",
"sinon": "^17.0.1"
}
}

View File

@@ -0,0 +1,94 @@
#! /bin/bash
# Requirements
# Docker: Node v18-alpine
function build_docker(){
if [[ $# -lt 1 ]]; then
echo $0 "[conatiner name]"
return -1
fi
CONTAINER_NAME=$1
cat src/config/apiConfig.json > src/config/apiConfig_local.json
set -x
docker rmi -f "$AWS_ECR_REPO/$CONTAINER_NAME"
docker buildx build --no-cache -t $AWS_ECR_REPO/$CONTAINER_NAME ./
set +x
}
function upload_image(){
#Global ENV VAR: AWS_DEFAULT_REGION
#Global ENV VAR: AWS_ECRWRITTER_ID
#Global ENV VAR: AWS_ECRWRITTER_SECRET
#Global ENV VAR: AWS_ECR_USER
#Global ENV VAR: AWS_ECR_REPO
#Global ENV VAR: CONTAINER_NAME
mkdir .aws
echo "[default]" > ./.aws/config
echo "region = $AWS_DEFAULT_REGION" >> ./.aws/config
echo "output = json" >> ./.aws/config
echo "[default]" > ./.aws/credentials
echo "aws_access_key_id = $AWS_ECRWRITTER_ID" >> ./.aws/credentials
echo "aws_secret_access_key = $AWS_ECRWRITTER_SECRET" >> ./.aws/credentials
DOCKER_PWD=$(docker run --rm -v ./.aws/:/root/.aws amazon/aws-cli ecr get-login-password)
rm -rf ./.aws
docker login -u $AWS_ECR_USER -p $DOCKER_PWD $AWS_ECR_REPO
set -x
docker push "$AWS_ECR_REPO/$CONTAINER_NAME":latest
set +x
}
function prepare_deployment(){
# Global Env Var: AWS_DEFAULT_REGION
# Global Env Var: AWS_ECRWRITTER_ID
# Global Env Var: AWS_ECRWRITTER_SECRET
# Global Env Var: SYSTEM_HOSTNAME
#Generate Docker Access Token
mkdir .aws
echo "[default]" > ./.aws/config
echo "region = $AWS_DEFAULT_REGION" >> ./.aws/config
echo "output = json" >> ./.aws/config
echo "[default]" > ./.aws/credentials
echo "aws_access_key_id = $AWS_ECRWRITTER_ID" >> ./.aws/credentials
echo "aws_secret_access_key = $AWS_ECRWRITTER_SECRET" >> ./.aws/credentials
export DOCKER_PWD=$(docker run --rm -v ./.aws/:/root/.aws amazon/aws-cli ecr get-login-password)
rm -rf ./.aws
}
function deploy_uservice(){
# CONTAINER NAME
# PUBLIC PORT
# PRIVATE PORT
# ECR REPO
if [[ $# -lt 4 ]]; then
echo "$0 [container_name] [public port] [private port] [ecr_repo]"
return -1
fi
container_name=$1
public_port=$2
private_port=$3
ecr_repo=$4
docker stop $container_name && docker rm $container_name
docker run -p"$public_port:$private_port" -d --restart unless-stopped --name $container_name $ecr_repo/$container_name:latest
}
function deploy_local(){
# docker login --username $AWS_ECR_USER --password $DOCKER_PWD $AWS_ECR_REPO
deploy_uservice $CONTAINER_NAME $PUBLIC_PORT $PRIVATE_PORT $AWS_ECR_REPO
}
function deploy(){
# Global Env Var: AWS_ECR_USER
# Global Env Var: AWS_ECR_REPO
# Global Env Var: CONTAINER_NAME
# Global Env Var: PUBLIC_PORT
# Global Env Var: PRIVATE_PORT
# Global Env Var: AWS_ECR_REPO
# Global Env Var: SYSTEM_HOSTNAME
prepare_deployment
set -x
ssh -i ~/.ssh/gitlab_runner gitlab-runner@$SYSTEM_HOSTNAME "docker login --username $AWS_ECR_USER --password $DOCKER_PWD $AWS_ECR_REPO && ./deploy_uservice.sh $CONTAINER_NAME $PUBLIC_PORT $PRIVATE_PORT $AWS_ECR_REPO && exit"
#deploy_local
set +x
}

10
v1/src/apps/index.js Normal file
View File

@@ -0,0 +1,10 @@
const express = require('express');
const app = express();
const private = require('./private');
const public = require('./public');
app.use( public );
app.use( private );
module.exports = app;

View File

@@ -0,0 +1,7 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.post('/register', services.register);
module.exports = router;

View File

@@ -0,0 +1,15 @@
"use strict";
const { ROOT_PATH, HANDLERS_PATH } = process.env;
const { complete_register } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Account` );
const register = async( req, res ) => {
try{
const result = await complete_register( req.context.userId , req.body );
return res.send( result );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
module.exports = { register };

View File

@@ -0,0 +1,12 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.post('/new', services.postBranch);
router.patch('/:id', services.patchBranch);
router.delete('/:id', services.deleteBranch);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,147 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = getModel('branches');
const populate_list = ['company', 'categories'];
const generic = new GenericHandler( Model, null, populate_list );
function getAndFilterList( query ){
const filter_list = [];
const { categories, branch_name, phone, city, state, truck_type } = query;
if( categories ) { filter_list.push({ categories }); }
if( branch_name ) { filter_list.push({ branch_name }); }
if( phone ) { filter_list.push({ phone }); }
if( city ) { filter_list.push({ city }); }
if( state ) { filter_list.push({ state }); }
if( truck_type ) { filter_list.push({ truck_type }); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findElements( companyId , query ){
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query );
let filter;
if( andFilterList ){
andFilterList.push({ company : companyId });
filter = { $and : andFilterList };
}else{
filter = { company : companyId };
}
const { total , limit, skip, data } = await generic.getList( page , elements, filter );
const list_elements = data;
// for(let i=0; i<list_elements.length; i++){
// const item = list_elements[i].toObject();
// if (item.categories) {
// let categories = item.categories.map((c) => c.name);
// item._categories = categories.join(", ");
// }
// if (item.truck_type) {
// item._truck_types = item.truck_type.join(", ");
// }
// list_elements[i] = item;
// }
return {
total,
limit,
skip,
data:list_elements
};
}
async function findElementById( elementId , companyId ){
const filter = {
$and : [
{ _id : elementId },
{ company : companyId }
]
};
let retVal = await Model.findOne( filter ).populate( populate_list ) || {};
return retVal;
}
const findList = async(req, res) => {
try{
const query = req.query || {};
const companyId = req.context.companyId;
const retVal = await findElements( companyId , query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const getById = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
res.send( await findElementById( elementId , companyId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const patchBranch = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
const data = req.body;
const branch = await findElementById( elementId , companyId );
if( !branch ){
throw "You can't modify this branch";
}
if( !data ){
throw "load data not sent";
}
data.company = companyId;
await Model.findByIdAndUpdate( elementId , data );
return res.send( await Model.findById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const postBranch = async(req, res) => {
try{
const companyId = req.context.companyId;
const data = req.body;
if( !data ){
throw "Branch data not sent";
}
data.company = companyId;
const branch = new Model( data );
await branch.save();
return res.send( branch );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const deleteBranch = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
const element = await findElementById( elementId , companyId );
if(!element){
throw "You can't delete this branch";
}
await Model.findByIdAndDelete( elementId );
return res.send(element);
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { findList, getById, patchBranch, postBranch, deleteBranch };

View File

@@ -0,0 +1,12 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.post('/new', services.postBudget);
router.patch('/:id', services.patchBudget);
router.delete('/:id', services.deleteBudget);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,178 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getModel } = require( '../../../lib/Models' );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = getModel('budgets');
const populate_list = ['company'];
const generic = new GenericHandler( Model, null, populate_list );
function getAndFilterList( query ){
const filter_list = [];
const {
client,
material,
origin,
destination,
truck_type,
num_tons,
price_per_ton,
tonnage,
pickup_distance,
delivery_distance,
warehouse_distance,
total_km_travel,
cost_per_liter,
fuel_price_per_liter,
other_fuel_expenses,
total_fuel_consumed,
total_cost_fuel,
driver_salary,
accomadation_allowance,
other_administrative_expenses,
total_before_tax,
total_utility_per_km,
total_profit,
profit_percentage,
total_administrative_expenses,
} = query;
if( client ) { filter_list.push({ client }); }
if( material ) { filter_list.push({ material }); }
if( origin ) { filter_list.push({ origin }); }
if( destination ) { filter_list.push({ destination }); }
if( truck_type ) { filter_list.push({ truck_type }); }
if( num_tons ) { filter_list.push({ num_tons }); }
if( price_per_ton ) { filter_list.push({ price_per_ton }); }
if( tonnage ) { filter_list.push({ tonnage }); }
if( pickup_distance ) { filter_list.push({ pickup_distance }); }
if( delivery_distance ) { filter_list.push({ delivery_distance }); }
if( warehouse_distance ) { filter_list.push({ warehouse_distance }); }
if( total_km_travel ) { filter_list.push({ total_km_travel }); }
if( cost_per_liter ) { filter_list.push({ cost_per_liter }); }
if( fuel_price_per_liter ) { filter_list.push({ fuel_price_per_liter }); }
if( other_fuel_expenses ) { filter_list.push({ other_fuel_expenses }); }
if( total_fuel_consumed ) { filter_list.push({ total_fuel_consumed }); }
if( total_cost_fuel ) { filter_list.push({ total_cost_fuel }); }
if( driver_salary ) { filter_list.push({ driver_salary }); }
if( accomadation_allowance ) { filter_list.push({ accomadation_allowance }); }
if( other_administrative_expenses ) { filter_list.push({ other_administrative_expenses }); }
if( total_before_tax ) { filter_list.push({ total_before_tax }); }
if( total_utility_per_km ) { filter_list.push({ total_utility_per_km }); }
if( total_profit ) { filter_list.push({ total_profit }); }
if( profit_percentage ) { filter_list.push({ profit_percentage }); }
if( total_administrative_expenses ) { filter_list.push({ total_administrative_expenses }); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findElements( companyId , query ){
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query );
let filter;
if( andFilterList ){
andFilterList.push({ company : companyId });
filter = { $and : andFilterList };
}else{
filter = { company : companyId };
}
const { total , limit, skip, data } = await generic.getList( page , elements, filter );
return {
total,
limit,
skip,
data:data
};
}
async function findElementById( elementId , companyId ){
const filter = {
$and : [
{ _id : elementId },
{ company : companyId }
]
};
let retVal = await Model.findOne( filter ).populate( populate_list ) || {};
return retVal;
}
const findList = async(req, res) => {
try{
const query = req.query || {};
const companyId = req.context.companyId;
const retVal = await findElements( companyId , query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const getById = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
res.send( await findElementById( elementId , companyId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const patchBudget = async(req, res) => {
try{
const elementId = req.params.id;
const budget = await Model.findById( elementId );
const data = req.body;
if( !budget ){
throw "You can't modify this budget";
}
if( !data ){
throw "budget data not sent";
}
await Model.findByIdAndUpdate( elementId , data );
return res.send( await Model.findById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const postBudget = async(req, res) => {
try{
const companyId = req.context.companyId;
const data = req.body;
if( !data ){
throw "budget data not sent";
}
data.company = companyId;
const budget = new Model( data );
await budget.save();
return res.send( budget );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const deleteBudget = async(req, res) => {
try{
const elementId = req.params.id;
const budget = await Model.findById( elementId );
if(!budget){
throw "You can't delete this budget";
}
await Model.findByIdAndDelete( elementId );
return res.send(budget);
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { findList, getById, patchBudget, postBudget, deleteBudget };

View File

@@ -0,0 +1,15 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/own', services.getOwnCompany);
router.patch('/own', services.patchOwnCompany);
router.get('/shipper', services.getListShippers);
router.get('/carrier', services.getListCarriers);
router.get('/users/:companyId', services.getUserLists);
router.get('/:id', services.getCompanyById);
module.exports = router;

View File

@@ -0,0 +1,229 @@
"use strict";
const { ROOT_PATH, MODELS_PATH, HANDLERS_PATH, LIB_PATH } = process.env;
const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` );
const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler.js' );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const usersModel = getModel('users');
const companiesModel = getModel('companies');
const branchesModel = getModel('branches');
const vehiclesModel = getModel('vehicles');
const loadsModel = getModel('loads');
const productCategoriesModel = getModel('product_categories');
const populate_select = {
categories:"-_id name",
};
const generic = new GenericHandler( companiesModel, null, null , populate_select );
const user_generic = new GenericHandler( usersModel );
function join_field_list( obj_with_fields , list_of_fields )
{
for(let field_idx=0; field_idx < list_of_fields.length; field_idx++){
const field_name = list_of_fields[ field_idx ];
const new_field_name = "_" + list_of_fields[ field_idx ];
if( obj_with_fields[ field_name ] ){
obj_with_fields[ new_field_name ] = obj_with_fields[field_name].join(", ");
}
}
return obj_with_fields;
}
function getAndFilterList( query ){
const filter_list = [];
const { company_type, company_name, truck_type, categories, company_state, company_city } = query;
if( company_name ){ filter_list.push( { company_name } ); }
if( company_type ){ filter_list.push( { company_type } ); }
if( company_state ){ filter_list.push( { company_state } ); }
if( company_city ){ filter_list.push( { company_city } ); }
if( truck_type ){ filter_list.push( { truck_type } ); }
if( categories ){ filter_list.push( { categories } ); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function getListByType( type , req ){
const filter = { "company_type" : type , "is_hidden" : false };
const select = [
"rfc",
"company_name",
"company_type",
"company_code",
"company_city",
"company_state",
"createdAt",
"membership",
"categories",
"truck_type",
"company_description"
];
const { $sort } = req.query;
const { elements , page } = getPagination( req.query );
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const andFilterList = getAndFilterList( req.query );
if( andFilterList ){
filter.$and = andFilterList;
}
const queryVal = await generic.getList(page , query_elements, filter, select, $sort );
const data_list = queryVal.data;
for(let i=0; i<data_list.length; i++){
data_list[i] = data_list[i].toObject();
data_list[i] = join_field_list( data_list[i] , ["company_city","company_state","truck_type"] );
let categories = data_list[i].categories.map( ( c ) => c.name);
data_list[i]._truck_types = data_list[i]._truck_type;
data_list[i]._categories = categories.join(", ");
/** Remove not requried fields */
delete data_list[i].categories;
delete data_list[i].company_city;
delete data_list[i].company_state;
delete data_list[i].truck_type;
delete data_list[i]._truck_type;
}
const retVal = {
total : queryVal.total,
limit : queryVal.limit,
skip : queryVal.skip,
data : data_list
};
return retVal;
}
async function getOwnCompany( req , res ) {
try{
const companyId = req.context.companyId;
const result = await companiesModel.findById( companyId )
.populate("categories" , "_id name")
.populate("company_city", "-_id name")
.populate("company_state", "-_id name")
.populate("truck_type", "-_id name");
return res.send( result );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
async function patchOwnCompany( req , res ) {
try{
const companyId = req.context.companyId;
const data = req.body;
if( data.company_type ){ delete data.company_type; }
await companiesModel.findByIdAndUpdate( companyId , data );
const result = await companiesModel.findById( companyId );
return res.send( result );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
async function getCompanyById( req , res ) {
try{
const companyId = req.params.id;
const result = await companiesModel.findById( companyId );
return res.send( result );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
async function getListShippers( req , res ) {
try{
const retVal = await getListByType( "Shipper" , req );
res.send( retVal );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
async function getListCarriers( req , res ) {
try{
const retVal = await getListByType( "Carrier" , req );
res.send( retVal );
}catch( error ){
console.error( error );
return res.status( 500 ).send({ error });
}
}
const getUserLists = async(req, res) => {
try{
const companyId = req.params.companyId;
const { elements, page } = getPagination( req.query );
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const select = [
"first_name",
"middle_name",
"last_name",
"company",
"employe_id",
"phone",
"phone2",
"email",
"categories",
"user_city",
"user_state",
"truck_type"
];
const queryVal = await user_generic.getList(page , query_elements, { company : companyId }, select );
const data_list = queryVal.data;
for(let i=0; i<data_list.length; i++){
data_list[i] = data_list[i].toObject();
let name;
name = ( !data_list[i].first_name )? "" : data_list[i].first_name;
name += ( !data_list[i].middle_name )? "": " " + data_list[i].middle_name;
name += ( !data_list[i].last_name )? "": " " + data_list[i].last_name;
data_list[i].name = name;
data_list[i] = join_field_list( data_list[i] , ["categories","user_city","user_state","truck_type"] );
// let categories = data_list[i].categories.map( ( c ) => c.name);
/** Remove not requried fields */
delete data_list[i].categories;
delete data_list[i].user_city;
delete data_list[i].user_state;
delete data_list[i].truck_type;
}
const retVal = {
total : queryVal.total,
limit : queryVal.limit,
skip : queryVal.skip,
data : data_list
};
return res.status(200).send( retVal );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "Companies: Internal error" });
}
};
module.exports = {
getOwnCompany,
patchOwnCompany,
getCompanyById,
getListShippers,
getListCarriers,
getUserLists
};

View File

@@ -0,0 +1,45 @@
'use strict';
const { ROOT_PATH , LIB_PATH } = process.env;
/// Router instance
const router = require('express').Router();
const jwtValidator = require( `${ROOT_PATH}/${LIB_PATH}/jwtValidator.js` );
const context = require( './lib/context' );
const account = require('./account/routes.js');
const budgets = require('./budgets/routes.js')
const branches = require('./branches/routes.js');
const companies = require('./companies/routes.js');
const loadAttachments = require('./load-attachments/routes.js');
const loads = require('./loads/routes.js');
const proposals = require('./proposals/routes.js');
const users = require('./users/routes.js');
const vehicles = require('./vehicles/routes.js');
router.use( jwtValidator.middleware );
router.use( context.middleware );
router.use('/account', account);
router.use('/budgets', budgets);
router.use('/branches', branches);
router.use('/companies', companies);
router.use('/load-attachments', loadAttachments );
router.use('/loads', loads);
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;

View File

@@ -0,0 +1,23 @@
'use strict';
const { ROOT_PATH, MODELS_PATH } = process.env;
const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` );
const usersModel = getModel('users');
async function middleware( req, res, next ){
if( ! req.JWT?.isValid ){
return res.status(401).send({error:"Unauthorized",code:401});
}
const userID = req.JWT.payload.sub;
req.context = {
user : await usersModel.findById( userID , { password : 0 , session_token : 0 , session_token_exp : 0 } ).populate('company')
}
req.context.userId = req.context.user.id;
req.context.companyId = req.context.user.company.id || null;
req.context.job_role = req.context.user.job_role || null;
req.context.permissions = req.context.user.permissions || null;
next();
}
module.exports = {
middleware
};

View File

@@ -0,0 +1,11 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.post('/loading/:id', services.postLoadingAttachment );
router.post('/downloading/:id', services.postDownloadingAttachment );
router.get('/load/:id', services.getLoadAttachmentList );
router.get('/:id', services.getAttachment );
router.get('/', services.getAttachmentList );
module.exports = router;

View File

@@ -0,0 +1,164 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, API_CONFIG } = process.env;
const { getPagination , getPage } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3Client = new S3Client({
region : apiConfig.S3.region,
credentials : {
accessKeyId : apiConfig.S3.accessKeyId,
secretAccessKey : apiConfig.S3.secretAccessKey
}
});
const s3Bucket = apiConfig.S3.bucket;
const s3BucketKey = apiConfig.S3.load_attachments_key;
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/load-attachments.model.js` );
const UserModel = require( `${ROOT_PATH}/${MODELS_PATH}/users.model.js` );
const LoadsModel = require( `${ROOT_PATH}/${MODELS_PATH}/loads.model.js` );
async function getAuthorizationFilter( userId ){
const user = await UserModel.findById( userId );
const companyId = user.company.toString();
return {
$or: [
{ company : companyId },
{ carrier : companyId },
]
};
}
const getAttachment = async(req, res) => {
try{
const attachmentId = req.params.id;
const CompanyAccessFilter = await getAuthorizationFilter( req.JWT.payload.sub );
const filter = {
$and : [
{ _id : attachmentId },
CompanyAccessFilter
]
};
const retVal = await Model.findOne( filter ) || {};
res.send( retVal );
}catch( err ){
res.send( {} );
}
};
const getAttachmentList = async(req, res) => {
const filter = await getAuthorizationFilter( req.JWT.payload.sub );
const { page , elements } = getPagination( req.query );
const retVal = await getPage( page, elements, Model, filter );
res.send( retVal );
};
const getLoadAttachmentList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const loadId = req.params.id;
try{
const CompanyAccessFilter = await getAuthorizationFilter( req.JWT.payload.sub );
const filter = {
$and : [
{ load : loadId },
CompanyAccessFilter
]
};
const retVal = await getPage( page, elements, Model, filter );
return res.send( retVal );
}catch( error ){
console.log( error );
return res.send({
total: 0,
limit: elements,
skip: page*elements,
data : []
});
}
};
async function getLoadById( loadId , companyId ){
const filter = {
$and : [
{ _id : loadId },
{
$or: [
{ company : companyId },
{ carrier : companyId },
]
}
]
};
return await LoadsModel.findOne( filter ) || null;
}
async function createLoadAttachment( type , userId , loadId ){
const user = await UserModel.findById( userId );
const companyId = user.company.toString();
const load = await getLoadById( loadId , companyId );
const prevAttachment = (load)? await Model.findOne({ load : loadId , type : type }) : null;
let attachment = null;
if( load && !prevAttachment ){
attachment = new Model({
type : type,
carrier : companyId,
company : load.company,
load : loadId,
author : userId
});
await attachment.save();
}
else if( load && prevAttachment ){
prevAttachment.updatedAt = Date.now();
await prevAttachment.save();
attachment = prevAttachment;
}else{
/**
* load is not valid => I don't have access to this load!
*/
attachment = null;
}
return attachment;
}
async function uploadFile( bucket, key, file , obj_id ){
const params = {
Bucket: bucket,
Key : `${key}/${obj_id}`,
ContentType : file.mimetype,
Body : file.data
};
const s3resp = await s3Client.send( new PutObjectCommand( params ) );
return s3resp;
}
const postLoadingAttachment = async(req, res) => {
const loadId = req.params.id;
const attachment = await createLoadAttachment( "Loading", req.JWT.payload.sub , loadId );
const file = req.files.attachment;
if( attachment && file ){
const s3resp = await uploadFile( s3Bucket, s3BucketKey, file , attachment._id );
res.send( attachment );
}else if( !file ){
res.status(400).send({ error : "attachment file not found" , code: 400 });
}else{
res.status(401).send({error:"Unauthorized",code:401});
}
};
const postDownloadingAttachment = async(req, res) => {
const loadId = req.params.id;
const attachment = await createLoadAttachment( "Downloading", req.JWT.payload.sub , loadId );
const file = req.files.attachment;
if( attachment && file ){
const s3resp = await uploadFile( s3Bucket, s3BucketKey, file , attachment._id );
res.send( attachment );
}else if( !file ){
res.status(400).send({ error : "attachment file not found" , code: 400 });
}else{
res.status(401).send({error:"Unauthorized",code:401});
}
};
module.exports = { getAttachment, getAttachmentList, getLoadAttachmentList, postLoadingAttachment, postDownloadingAttachment };

View File

@@ -0,0 +1,13 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.get('/calendar', services.findCalendarList);
router.post('/new', services.postLoad);
router.patch('/:id', services.patchLoad);
router.delete('/:id', services.deleteLoad);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,294 @@
"use strict";
const { ROOT_PATH, LIB_PATH } = process.env;
const { getModel } = require( '../../../lib/Models' );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler.js' );
const Model = getModel('loads');
const CompanyModel = getModel('companies');
const ProposalsModel = getModel('proposals');
const populate_list = ['product', 'company', 'carrier', 'vehicle', 'categories'];
const generic = new GenericHandler( Model, null, populate_list );
function getAndFilterList( query ){
const filter_list = [];
const {
companyId,
carrier,
vehicle,
driver,
status,
posted_by,
posted_by_name,
load_status,
published_date,
loaded_date,
transit_date,
categories,
product,
shipment_code
} = query;
if( companyId ){ filter_list.push( { company : companyId } ); }
if( carrier ){ filter_list.push( { carrier } ); }
if( vehicle ){ filter_list.push( { vehicle } ); }
if( driver ){ filter_list.push( { driver } ); }
if( status ){ filter_list.push( { status } ); }
if( posted_by ) { filter_list.push({ posted_by }); }
if( posted_by_name ) { filter_list.push({ posted_by_name }); }
if( load_status ) { filter_list.push({ load_status }); }
if( published_date ) { filter_list.push({ published_date }); }
if( loaded_date ) { filter_list.push({ loaded_date }); }
if( transit_date ) { filter_list.push({ transit_date }); }
if( categories ) { filter_list.push({ categories }); }
if( product ) { filter_list.push({ product }); }
if( shipment_code ) { filter_list.push({ shipment_code }); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findLoads( query ){
const { $sort, company_name } = query;
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query ) || [];
let filter;
if( company_name ){
/* Populate list of company ids with match on the company_name */
const company_list = await CompanyModel.find( { company_name }, [ "id" ] );
const or_company_list = []
company_list.forEach( (item) =>{
or_company_list.push({"company" : item.id});
})
andFilterList.push({
$or : or_company_list
});
}
if( andFilterList.length > 0 ){
filter = { $and : andFilterList };
}else{
filter = null;
}
const { total , limit, skip, data } = await generic.getList( page , elements, filter, null, $sort );
const load_list = data;
for(let i=0; i<load_list.length; i++){
const load_id = load_list[ i ].id;
load_list[i] = load_list[i].toObject();
const no_of_proposals = await ProposalsModel.count({ load : load_id });
load_list[i].no_of_proposals = no_of_proposals;
}
return {
total,
limit,
skip,
data : load_list
};
}
async function findCalendarLoads( userId, companyId, query ){
const select = [
"company",
"carrier",
"posted_by",
"bidder",
"status",
"load_status",
"published_date",
"load_status_updated",
"loaded_date",
"transit_date",
"delivered_date",
"createdAt",
"shipment_code",
"est_loading_date",
"est_unloading_date"
];
const { date, load_status , $sort } = query;
const { global } = query;
if( ! date ){
throw "Date field is required";
}
const { page, elements } = getPagination( query );
const orFilterList = [
{"company" : companyId},
{"carrier" : companyId}
]
const andFilterList = [
{
"est_loading_date" : {
$gte : new Date( date["gte"] ),
$lte : new Date( date["lte"] )
}
},
{
$or : orFilterList
}
]
if( !(global) || (global == 0) ){
andFilterList.push( {
$or : [
{"bidder" : userId},
{"posted_by" : userId},
]
} );
}
if( load_status ){
andFilterList.push( { load_status } )
}
const filter = {
$and : andFilterList,
};
const {
total,
limit,
skip,
query : model_query,
} = await generic.getListQuery( page , elements, filter , select );
if( $sort ){
model_query.sort( $sort );
}
const data = await model_query.exec();
return {
total,
limit,
skip,
data
};
}
async function findElementById( elementId ){
let retVal = await Model.findById( elementId ).populate( populate_list );
if( retVal ){
retVal = retVal.toObject();
const no_of_proposals = await ProposalsModel.count({ load : elementId });
retVal.no_of_proposals = no_of_proposals;
}else{
retVal = {};
}
return retVal;
}
const findCalendarList = async(req, res) => {
try{
const query = req.query || {};
const companyId = req.context.companyId;
const userId = req.context.userId;
const retVal = await findCalendarLoads( userId, companyId, query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
}
const findList = async(req, res) => {
try{
const query = req.query || {};
const retVal = await findLoads( query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const getById = async(req, res) => {
try{
const elementId = req.params.id;
res.send( await findElementById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const patchLoad = async(req, res) => {
try{
const elementId = req.params.id;
const permissions = req.context.permissions;
const data = req.body;
const load = await findElementById( elementId );
if( !load ){
throw "You can't modify this load";
}
if( !data ){
throw "load data not sent";
}
await Model.findByIdAndUpdate( elementId , data );
return res.send( await Model.findById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const postLoad = async(req, res) => {
try{
const companyId = req.context.companyId;
const userId = req.context.userId;
const user_name = req.context.user.first_name;
const permissions = req.context.permissions;
const data = req.body;
if( !data ){
throw "Load data not sent";
}
if(permissions !== "role_shipper" ){
throw "You can't create loads";
}
data.company = companyId;
data.posted_by = userId;
data.name = user_name;
const load = new Model( data );
await load.save();
const id = "" + load._id;
const shipment_code = "ETA-" + id.substring( 0 , 6 );
await Model.findByIdAndUpdate( id , {
shipment_code
});
return res.send( load );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const deleteLoad = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
const permissions = req.context.permissions;
const load = await findElementById( elementId , companyId );
if(!load){
throw "You can't delete this load";
}
if(permissions !== "role_shipper" ){
throw "You can't delete loads";
}
await Model.findByIdAndDelete( elementId );
return res.send(load);
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { findCalendarList, findList, getById, patchLoad, postLoad, deleteLoad };

View File

@@ -0,0 +1,12 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.post('/new', services.postProposal);
router.patch('/:id', services.patchProposal);
router.delete('/:id', services.deleteProposal);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,142 @@
"use strict";
const { getModel } = require( '../../../lib/Models' );
const { getPagination } = require( '../../../lib/Misc' );
const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler' );
const { onPatchEvent } = require('../../../lib/Handlers/Proposals.handler');
const Model = getModel('proposals');
const populate_list = [
{ path:'load' , populate : { path : 'categories' } },
{ path:'load' , populate : { path : 'company' } },
'shipper',
'carrier',
'vehicle',
'bidder',
'accepted_by'
];
const generic = new GenericHandler( Model, null, populate_list );
function getAndFilterList( query ){
const filter_list = [];
const { load, categories, branch_name, phone, city, state, truck_type } = query;
if( load ) { filter_list.push({ load }); }
if( categories ) { filter_list.push({ categories }); }
if( branch_name ) { filter_list.push({ branch_name }); }
if( phone ) { filter_list.push({ phone }); }
if( city ) { filter_list.push({ city }); }
if( state ) { filter_list.push({ state }); }
if( truck_type ) { filter_list.push({ truck_type }); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findElements( query ){
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query );
let filter;
if( andFilterList ){
filter = { $and : andFilterList };
}else{
filter = null;
}
const { total , limit, skip, data } = await generic.getList( page , elements, filter );
return {
total,
limit,
skip,
data:data
};
}
async function findElementById( elementId , companyId ){
const filter = {
$and : [
{ _id : elementId },
{ $or :[
{ shipper : companyId },
{ carrier : companyId }
]}
]
};
let retVal = await Model.findOne( filter ).populate( populate_list ) || {};
return retVal;
}
const findList = async(req, res) => {
try{
const query = req.query || {};
const retVal = await findElements( query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const getById = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
res.send( await findElementById( elementId , companyId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const patchProposal = async(req, res) => {
try{
const elementId = req.params.id;
const proposal = await Model.findById( elementId );
const data = req.body;
if( !proposal ){
throw "You can't modify this proposal";
}
if( !data ){
throw "proposal data not sent";
}
await Model.findByIdAndUpdate( elementId , data );
await onPatchEvent( elementId , data );
return res.send( await Model.findById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const postProposal = async(req, res) => {
try{
const data = req.body;
if( !data ){
throw "proposal data not sent";
}
const proposal = new Model( data );
await proposal.save();
return res.send( proposal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const deleteProposal = async(req, res) => {
try{
const elementId = req.params.id;
const proposal = await Model.findById( elementId );
if(!proposal){
throw "You can't delete this proposal";
}
await Model.findByIdAndDelete( elementId );
return res.send(proposal);
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { findList, getById, patchProposal, postProposal, deleteProposal };

View File

@@ -0,0 +1,16 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.post('/member', services.postTeamMemberData);
router.patch('/member/:id', services.patchTeamMemberProfileData);
router.delete('/member/:id', services.deleteTeamMember);
router.get('/profile', services.getProfileData);
router.patch('/profile', services.patchProfileData);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,177 @@
"use strict";
const { ROOT_PATH, HANDLERS_PATH } = process.env;
const { getUserById, findUsers, patchUserData, createUserWithinCompany, deleteUserWithinCompany } = require( "../../../lib/Handlers/Users.handler" );
const findList = async(req, res) => {
try{
const {
total,
limit,
skip,
data
} = await findUsers( req.query );
return res.send({
total,
limit,
skip,
data});
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
};
const getById = async(req, res) => {
try{
const id = req.params.id;
const user = await getUserById( id );
res.send({ user });
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
};
const getProfileData = async(req, res) => {
res.send( req.context.user );
};
const patchProfileData = async(req, res) => {
try{
const data = req.body;
if( (data.email) && (data.email === req.context.user.email ) ){
delete data.email;
}
if( req.body.job_role ){
/// You can't change your own role
delete data.job_role;
}
const user = await patchUserData( req.context.user.id , data );
res.send( user );
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
};
function job_role_change_allowance( change_author_job_role , affected_job_role ){
try{
if( (change_author_job_role !== "owner") && (change_author_job_role !== "manager") ){
return false;
}
if( affected_job_role === "owner" ){
return false;
}
switch( affected_job_role ){
case 'manager':
case 'driver':
case 'staff':
return true;
default:
return false;
}
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
const patchTeamMemberProfileData = async(req, res) => {
try{
const id = req.params.id;
if( id === req.context.userId ){
return await patchProfileData( req, res);
}
const companyId = req.context.companyId;
if( !companyId ){
return res.status(400).send( { error : "Not authorized to modify this user" } );
}
/// If a job_role change is requested, validate with rules.
if( ( req.body.job_role ) &&
( !job_role_change_allowance( req.context.job_role , req.body.job_role ) )
){
return res.status(400).send( { error : "Not authorized to upgrade the role as requested" } );
}
if( ( req.body.job_role ) && ( req.body.job_role === "driver" ) && (req.context.permissions !== "role_carrier" ) ){
return res.status(400).send( { error : "Your company can not create drivers" } );
}
if( (req.context.job_role !== "owner") && (req.context.job_role !== "manager") ){
/// Only an owner or manager can modify a team member.
return res.status(400).send( { error : "Your role does not allow to modify this user" } );
}
/// No one can modify an "owner".
const teamMember = await getUserById( id , { company : companyId , job_role : { $ne: "owner" } } );
if( !teamMember ){
return res.status(400).send( { error : "You can't modify users outside of your company" } );
}
/// Apply change to user.
const user_patch_result = await patchUserData( id , req.body );
return res.send( user_patch_result );
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
};
const postTeamMemberData = async(req, res) => {
try{
const companyId = req.context.companyId;
if( !companyId ){
return res.status(400).send( { error : "Not authorized to create users" } );
}
if( !req.body.job_role ){
return res.status(400).send( { error : "job_role is mandatory!" } );
}
if( ( req.body.job_role ) &&
( !job_role_change_allowance( req.context.job_role , req.body.job_role ) )
){
return res.status(400).send( { error : "Not authorized to create the role as requested" } );
}
if( (req.context.job_role !== "owner") && (req.context.job_role !== "manager") ){
return res.status(400).send( { error : "Not authorized to create users" } );
}
if( ( req.body.job_role ) && ( req.body.job_role === "driver" ) && (req.context.permissions !== "role_carrier" ) ){
return res.status(400).send( { error : "Your company can not create drivers" } );
}
if( !req.body.email ){
return res.status(400).send( { error : "email is mandatory to create a new user" } );
}
/// Only an owner or manager can create a new user
const teamMember = await createUserWithinCompany( companyId , req.body );
return res.send( teamMember );
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
const deleteTeamMember = async(req, res) => {
try{
const user_to_remove_id = req.params.id;
const manager_id = req.context.userId;
if( (req.context.job_role !== "owner") && (req.context.job_role !== "manager") ){
return res.status(400).send( { error : "Not authorized to delete this user" } );
}
const teamMember = await deleteUserWithinCompany( manager_id, user_to_remove_id );
return res.send( teamMember );
}catch( error ){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
module.exports = { findList , getById , getProfileData, patchProfileData, patchTeamMemberProfileData, postTeamMemberData , deleteTeamMember };

View File

@@ -0,0 +1,12 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/find', services.findList);
router.post('/new', services.postVehicle);
router.patch('/:id', services.patchVehicle);
router.delete('/:id', services.deleteVehicle);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,168 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = getModel('vehicles');
const populate_list = ['categories', 'active_load','load_shipper','driver'];
const generic = new GenericHandler( Model, null, populate_list );
function getAndFilterList( query ){
const filter_list = [];
const {
categories,
active_load,
load_shipper,
driver,
vehicle_code,
vehicle_name,
vehicle_number,
circulation_serial_number,
truck_type,
tyre_type,
city,
state,
status,
destino
} = query;
if( categories ) { filter_list.push({ categories }); }
if( active_load ) { filter_list.push({ active_load }); }
if( load_shipper ) { filter_list.push({ load_shipper }); }
if( driver ) { filter_list.push({ driver }); }
if( vehicle_code ) { filter_list.push({ vehicle_code }); }
if( vehicle_name ) { filter_list.push({ vehicle_name }); }
if( vehicle_number ) { filter_list.push({ vehicle_number }); }
if( circulation_serial_number ) { filter_list.push({ circulation_serial_number }); }
if( truck_type ) { filter_list.push({ truck_type }); }
if( tyre_type ) { filter_list.push({ tyre_type }); }
if( city ) { filter_list.push({ city }); }
if( state ) { filter_list.push({ state }); }
if( status ) { filter_list.push({ status }); }
if( destino ) { filter_list.push({ destino }); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findElements( companyId , query ){
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query );
let filter;
if( andFilterList ){
andFilterList.push({ company : companyId });
filter = { $and : andFilterList };
}else{
filter = { company : companyId };
}
const { total , limit, skip, data } = await generic.getList( page , elements, filter );
return {
total,
limit,
skip,
data:data
};
}
async function findElementById( elementId , companyId ){
let retVal = await Model.findById( elementId ).populate( populate_list ) || {};
return retVal;
}
const findList = async(req, res) => {
try{
const query = req.query || {};
const companyId = req.context.companyId;
const retVal = await findElements( companyId , query );
res.send( retVal );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const getById = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
res.send( await findElementById( elementId , companyId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const patchVehicle = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
const permissions = req.context.permissions;
const vehicle = await findElementById( elementId , companyId );
const data = req.body;
if( !vehicle ){
throw "You can't modify this vehicle";
}
if( !data ){
throw "Vehicle data not sent";
}
if( permissions !== "role_carrier" ){
throw "You can't modify vehicles";
}
data.company = companyId;
await Model.findByIdAndUpdate( elementId , data );
return res.send( await Model.findById( elementId ) );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const postVehicle = async(req, res) => {
try{
const userId = req.context.userId;
const companyId = req.context.companyId;
const permissions = req.context.permissions;
const data = req.body;
if( !data ){
throw "Vehicle data not sent";
}
if(permissions !== "role_carrier" ){
throw "You can't create vehicles";
}
data.company = companyId;
data.status = "Free";
data.is_available = false;
data.posted_by = userId;
const vehicle = new Model( data );
await vehicle.save();
return res.send( vehicle );
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
const deleteVehicle = async(req, res) => {
try{
const companyId = req.context.companyId;
const elementId = req.params.id;
const permissions = req.context.permissions;
const vehicle = await findElementById( elementId , companyId );
if( !vehicle ){
throw "You can't delete this vehicle";
}
if(permissions !== "role_carrier" ){
throw "You can't delete vehicles";
}
await Model.findByIdAndDelete( elementId );
return res.send(vehicle);
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { findList, getById, patchVehicle, postVehicle, deleteVehicle };

View File

@@ -0,0 +1,16 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.post('/authorize', services.AuthorizeJWT);
router.get('/authorize/:session_token', services.RenewJWT);
router.get('/check-account/:email', services.checkAccount );
router.post('/signup', services.TryCreateAccount);
router.patch('/signup', services.ConfirmAccount);
router.post('/recover', services.RecoverPwd);
router.patch('/recover', services.ConfirmRecoverPwd);
module.exports = router;

View File

@@ -0,0 +1,279 @@
"use strict";
const jsonwebtoken = require('jsonwebtoken');
const { API_CONFIG, ROOT_PATH, LIB_PATH, HANDLERS_PATH } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const { genKey, toSha256 } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { emailEvent , EMAIL_EVENTS } = require( '../../../lib/Handlers/MailClient' );
const { create_account, already_exists, verify_driver_account, login, login_with_session_token, reset_password } = require( '../../../lib/Handlers/Account' );
const { Validator } = require( "jsonschema" );
const jwtSecret = apiConfig.authentication.jwtSecret;
const jwtTimeout = apiConfig.authentication.jwtTimeout;//Timeout in hours
const jwtRenewalTimeout = apiConfig.authentication.jwtRenewalTimeout;//Timeout in hours
const jwtOptions = apiConfig.authentication.jwtOptions;
const validator = new Validator();
const create_account_schema = {
type : 'object',
properties : {
email : { type : 'string' , maxLength : 256 },
password : { type : 'string', maxLength : 256},
otp : { type : 'string', maxLength : 6 },
checksum : { type : 'string', maxLength : 32 }
},
required : [ 'email', 'password' ]
};
const confirm_account_schema = {
type : 'object',
properties : create_account_schema.properties,//Same properties
required : [ 'email', 'password', 'otp', 'checksum' ]//Different requirements
};
const login_account_schema = {
type : 'object',
properties : create_account_schema.properties,//Same properties
required : [ 'email', 'password' ]//Different requirements
};
const password_recover_schema = create_account_schema;
const confirm_password_recover_schema = {
type : 'object',
properties : create_account_schema.properties,//Same properties
required : [ 'email', 'password', 'otp', 'checksum' ]//Different requirements
};
async function AuthorizeJWT_email_pwd( email , password ){
const user = await login( email, password );
if( !user ){
return null;
}
const current_date = new Date();
const iat = Math.floor( (current_date.getTime())/1000 );
const renewal_exp = ( iat + 3600*jwtRenewalTimeout ) * 1000;
/**
* Renew session token on every login event.
* Previous session token is lost
*/
const session_token = toSha256( `${new Date()}` );
const session_token_exp = new Date( renewal_exp );
user.session_token = session_token;
user.session_token_exp = session_token_exp;
await user.save();
const payload = {
iat: iat,
exp: iat + jwtTimeout * 3600,
aud: jwtOptions.audience,
iss: jwtOptions.audience,
sub: user.id,
};
const jwt = jsonwebtoken.sign( payload , jwtSecret );
return {
accessToken : jwt,
payload : payload,
session_token,
session_token_exp,
user : user
};
}
const AuthorizeJWT = async(req, res) => {
try{
if( validator.validate( req.body , login_account_schema ).valid ){
const { email, password } = req.body;
const retVal = await AuthorizeJWT_email_pwd( email , password );
if( !retVal ){
return res.status(401).send( { error : "Invalid credentials" } );
}
return res.send( retVal );
}else{
return res.status(400).send( { error : "Invalid request" } );
}
}catch( err ){
console.error( err );
res.status(500).send({ error : "Login: Internal error" });
}
};
const RenewJWT = async(req, res) => {
try{
const login_session_token = req.params.session_token;
const user = await login_with_session_token( login_session_token );
if( !user ){
return res.status(401).send( { error : "Invalid or Expired Session Token" } );
}
const current_date = new Date();
const iat = Math.floor( (current_date.getTime())/1000 );
const renewal_exp = ( iat + 3600*jwtRenewalTimeout ) * 1000;
/**
* Renew session token on every login event.
* Previous session token is lost
*/
const session_token = toSha256( `${new Date()}` );
const session_token_exp = new Date( renewal_exp );
user.session_token = session_token;
user.session_token_exp = session_token_exp;
await user.save();
const payload = {
iat: iat,
exp: iat + jwtTimeout * 3600,
aud: jwtOptions.audience,
iss: jwtOptions.audience,
sub: user.id,
};
const jwt = jsonwebtoken.sign( payload , jwtSecret );
return res.status(200).send( {
accessToken : jwt,
payload : payload,
session_token,
session_token_exp,
user : user
} );
}catch( err ){
console.error( err );
res.status(500).send({ error : "Renew: Internal error" });
}
};
const TryCreateAccount = async(req, res) => {
try{
if( validator.validate( req.body , create_account_schema ).valid ){
const otp = genKey();
const { email : receiver , password } = req.body;
const email = receiver;
const it_exists = await already_exists( email );
if( it_exists ){
return res.status(400).send({ error : "Email already exists" });
}
const content = { OTP : otp, user_name : email };
const checksum_entry = {
email : receiver,
password,
otp
};
const checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32);
await emailEvent( EMAIL_EVENTS.ACCOUNT_VERIFY , receiver , content );
console.log(
content
);
res.status(200).send( { checksum } );
}else{
res.status(400).send( { error : "Invalid request" } );
}
}catch( err ){
console.error( err );
res.status(500).send({ error : "Account creation: Internal error" });
}
};
const ConfirmAccount = async(req, res) => {
try{
if( validator.validate( req.body , confirm_account_schema ).valid ){
const { email, password, otp, checksum } = req.body;
const it_exists = await already_exists( email );
if( it_exists ){
return res.status(400).send({ error : "User already registered!" });
}
const checksum_entry = {email, password, otp};
const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32);
if( recomputed_checksum != checksum ){
return res.status(400).send({ error : "Wrong OTP" });
}
await create_account( email, password );
const content = { user_name : email };
const receiver = email;
await emailEvent( EMAIL_EVENTS.ACCOUNT_CONFIRMED , receiver , content );
const retVal = await AuthorizeJWT_email_pwd( email , password );
return res.send( retVal );
}else{
return res.status(400).send( { error : "Invalid request" } );
}
}catch( err ){
console.error( err );
return res.status(500).send({ error : "Account creation: Internal error" });
}
};
const RecoverPwd = async(req, res) => {
try{
if( validator.validate( req.body , password_recover_schema ).valid ){
const otp = genKey();
const { email : receiver , password } = req.body;
const email = receiver;
const it_exists = await already_exists( email );
if( !it_exists ){
return res.status(400).send({ error : "Email is not registered!" });
}
const content = { OTP : otp, user_name : email };
const checksum_entry = {
email : receiver,
password,
otp
};
const checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32);
await emailEvent( EMAIL_EVENTS.ACCOUNT_VERIFY , receiver , content );
console.log(
content
);
res.status(200).send( { checksum } );
}else{
res.status(400).send( { error : "Invalid request" } );
}
}catch( err ){
console.error( err );
res.status(500).send({ error : "Password Recover: Internal error" });
}
};
const ConfirmRecoverPwd = async(req, res) => {
try{
if( validator.validate( req.body , confirm_password_recover_schema ).valid ){
const { email, password, otp, checksum } = req.body;
const it_exists = await already_exists( email );
if( !it_exists ){
return res.status(400).send({ error : "Email is not registered!" });
}
const checksum_entry = {email, password, otp};
const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32);
if( recomputed_checksum != checksum ){
return res.status(400).send({ error : "Wrong OTP" });
}
await reset_password( email, password );
return res.status(200).send( { msg : "Password is reset!" } );
}else{
return res.status(400).send( { error : "Invalid request" } );
}
}catch( err ){
console.error( err );
return res.status(500).send({ error : "Password Recover Confirmation: Internal error" });
}
};
const checkAccount = async(req, res) => {
try{
const email = req.params.email;
const driver_account_val = await verify_driver_account( email );
return res.status(200).send( driver_account_val );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "AuthManagement: Internal error" });
}
};
module.exports = { AuthorizeJWT, RenewJWT, TryCreateAccount, ConfirmAccount, RecoverPwd, ConfirmRecoverPwd, checkAccount};

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/cities.model.js` );
const generic = new GenericHandler( Model, "city_name" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,7 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.post('/', services.postSendConactEMail);
module.exports = router;

View File

@@ -0,0 +1,38 @@
"use strict";
const { ROOT_PATH, HANDLERS_PATH } = process.env;
const { emailEvent , EMAIL_EVENTS } = require( `${ROOT_PATH}/${HANDLERS_PATH}/MailClient` );
const { Validator } = require( "jsonschema" );
const validator = new Validator();
const conact_email = {
type : 'object',
properties : {
name : { type : 'string' , maxLength : 256 },
email : { type : 'string' , maxLength : 256 },
message : { type : 'string', maxLength : 1024 }
},
required : [ 'name', 'email', 'message' ]
};
const postSendConactEMail = async(req, res) => {
try{
if( validator.validate( req.body , conact_email ).valid ){
const receiver = req.body.email;
const content = {
name : req.body.name,
email : req.body.email,
message : req.body.message
}
await emailEvent( EMAIL_EVENTS.CONTACT_EMAIL , receiver , content );
}else{
res.status(400).send( { error : "Invalid request" } );
}
return res.status(200).send({ msg : "Email sent!"});
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "Contact-Email: Internal error" });
}
};
module.exports = { postSendConactEMail };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/countries.model.js` );
const generic = new GenericHandler( Model, "country_name" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,42 @@
'use strict';
const { ROOT_PATH , LIB_PATH } = process.env;
/// Router instance
const router = require('express').Router();
const account = require('./account/routes.js');
const cities = require('./cities/routes.js');
const contactEmail = require('./contact-email/routes.js');
const countries = require('./countries/routes.js');
const metaData = require('./meta-data/routes.js');
const metaGroups = require('./meta-groups/routes.js');
const news = require('./news/routes.js');
const productCategories = require('./product-categories/routes.js');
const products = require('./products/routes.js');
const publicCompanies = require('./public-companies/routes.js');
const publicVehicles = require('./public-vehicles/routes.js');
const publicLoads = require('./public-loads/routes.js');
const publicLoadAttachments = require('./public-load-attachments/routes.js');
const publicLoadsTracking = require('./public-load-tracking/routes.js');
const states = require('./states/routes.js');
const test = require('./test/routes.js');
router.use('/account', account);
router.use('/cities', cities);
router.use('/contact-email', contactEmail);
router.use('/countries', countries);
router.use('/meta-data', metaData);
router.use('/meta-groups', metaGroups);
router.use('/news', news);
router.use('/product-categories', productCategories);
router.use('/products', products);
router.use("/public-companies", publicCompanies);
router.use("/public-vehicles", publicVehicles);
router.use('/public-loads', publicLoads );
router.use('/public-load-attachments', publicLoadAttachments );
router.use('/public-load-tracking', publicLoadsTracking );
router.use('/states', states);
router.use("/test", test);
module.exports = router;

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/meta-data.model.js` );
const generic = new GenericHandler( Model, "meta_value" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/meta-groups.model.js` );
const generic = new GenericHandler( Model, "group_label" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,10 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
router.get('/download/:image_name', services.getImageByNewId);
module.exports = router;

View File

@@ -0,0 +1,52 @@
"use strict";
const { API_CONFIG, ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const { downloadFile } = require(`${ROOT_PATH}/${LIB_PATH}/Misc`);
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const s3Bucket = apiConfig.S3.bucket;
const s3BucketKey = apiConfig.S3.news_key;
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/news.model.js` );
const generic = new GenericHandler( Model );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
const getImageByNewId = async(req, res) => {
try{
const image_name = req.params.image_name;
const file = await downloadFile( s3Bucket, s3BucketKey, image_name );
res.attachment( image_name );
res.setHeader('Content-Type', file.ContentType );
res.send( file.Body );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "News: Internal error" });
}
};
module.exports = { getList , findList , getById, getImageByNewId };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/product-categories.model.js` );
const generic = new GenericHandler( Model, "name" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/products.model.js` );
const generic = new GenericHandler( Model, "name" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/shipper', services.getListShippers);
router.get('/carrier', services.getListCarriers);
router.get('/users/:companyId', services.getUserLists);
module.exports = router;

View File

@@ -0,0 +1,182 @@
"use strict";
const { query } = require("express");
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination , getPage } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/companies.model.js` );
const categoriesModel = require( `${ROOT_PATH}/${MODELS_PATH}/product-categories.model.js` );
const usersModel = require( `${ROOT_PATH}/${MODELS_PATH}/users.model.js` );
const populate_select = {
categories:"-_id name",
};
const generic = new GenericHandler( Model, null, null , populate_select );
const user_generic = new GenericHandler( usersModel );
function join_field_list( obj_with_fields , list_of_fields )
{
for(let field_idx=0; field_idx < list_of_fields.length; field_idx++){
const field_name = list_of_fields[ field_idx ];
const new_field_name = "_" + list_of_fields[ field_idx ];
if( obj_with_fields[ field_name ] ){
obj_with_fields[ new_field_name ] = obj_with_fields[field_name].join(", ");
}
}
return obj_with_fields;
}
function getAndFilterList( query ){
const filter_list = [];
const { company_type, company_name, truck_type, categories, company_state, company_city } = query;
if( company_name ){ filter_list.push( { company_name } ); }
if( company_type ){ filter_list.push( { company_type } ); }
if( company_state ){ filter_list.push( { company_state } ); }
if( company_city ){ filter_list.push( { company_city } ); }
if( truck_type ){ filter_list.push( { truck_type } ); }
if( categories ){ filter_list.push( { categories } ); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function getListByType( type , req ){
const filter = { "company_type" : type , "is_hidden" : false };
const select = [
"rfc",
"company_name",
"company_type",
"company_code",
"company_city",
"company_state",
"createdAt",
"membership",
"categories",
"truck_type",
"company_description"
];
const { elements } = getPagination( req.query );
const page = 0;// No pagination allowed to this endpoint
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const andFilterList = getAndFilterList( req.query );
if( andFilterList ){
filter.$and = andFilterList;
}
const queryVal = await generic.getList(page , query_elements, filter, select );
const data_list = queryVal.data;
for(let i=0; i<data_list.length; i++){
data_list[i] = data_list[i].toObject();
data_list[i] = join_field_list( data_list[i] , ["company_city","company_state","truck_type"] );
let categories = data_list[i].categories.map( ( c ) => c.name);
data_list[i]._truck_types = data_list[i]._truck_type;
data_list[i]._categories = categories.join(", ");
/** Remove not requried fields */
delete data_list[i].categories;
delete data_list[i].company_city;
delete data_list[i].company_state;
delete data_list[i].truck_type;
delete data_list[i]._truck_type;
}
const retVal = {
total : queryVal.total,
limit : queryVal.limit,
skip : queryVal.skip,
data : data_list
};
return retVal;
}
const getListShippers = async(req, res) => {
try{
const retVal = await getListByType( "Shipper" , req );
res.send( retVal );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "Public-Companies(Carriers): Internal error" });
}
};
const getListCarriers = async(req, res) => {
try{
const retVal = await getListByType( "Carrier" , req );
res.send( retVal );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "Public-Companies(Carriers): Internal error" });
}
};
const getUserLists = async(req, res) => {
try{
const companyId = req.params.companyId;
const { elements } = getPagination( req.query );
const page = 0;// No pagination allowed to this endpoint
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const select = [
"first_name",
"middle_name",
"last_name",
"company",
"employe_id",
"phone",
"phone2",
"email",
"categories",
"user_city",
"user_state",
"truck_type"
];
const queryVal = await user_generic.getList(page , query_elements, { company : companyId }, select );
const data_list = queryVal.data;
for(let i=0; i<data_list.length; i++){
data_list[i] = data_list[i].toObject();
let name;
name = ( !data_list[i].first_name )? "" : data_list[i].first_name;
name += ( !data_list[i].middle_name )? "": " " + data_list[i].middle_name;
name += ( !data_list[i].last_name )? "": " " + data_list[i].last_name;
data_list[i].name = name;
data_list[i] = join_field_list( data_list[i] , ["categories","user_city","user_state","truck_type"] );
let categories = data_list[i].categories.map( ( c ) => c.name);
/** Remove not requried fields */
delete data_list[i].categories;
delete data_list[i].user_city;
delete data_list[i].user_state;
delete data_list[i].truck_type;
}
const retVal = {
total : queryVal.total,
limit : queryVal.limit,
skip : queryVal.skip,
data : data_list
};
return res.status(200).send( retVal );
} catch ( err ){
console.error( err );
return res.status(500).send({ error : "Public-Companies: Internal error" });
}
};
module.exports = { getListShippers, getListCarriers, getUserLists };

View File

@@ -0,0 +1,7 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/download/:id', services.getAttachmentFile );
module.exports = router;

View File

@@ -0,0 +1,22 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, API_CONFIG } = process.env;
const { downloadFile } = require(`${ROOT_PATH}/${LIB_PATH}/Misc`);
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const s3Bucket = apiConfig.S3.bucket;
const s3BucketKey = apiConfig.S3.load_attachments_key;
const getAttachmentFile = async(req, res) => {
try{
const attachmentId = req.params.id;
const file = await downloadFile( s3Bucket, s3BucketKey, attachmentId );
res.attachment( attachmentId );
res.setHeader('Content-Type', file.ContentType );
res.send( file.Body );
}catch( error ){
console.error( error );
res.status(500).send({ error });
}
}
module.exports = { getAttachmentFile };

View File

@@ -0,0 +1,7 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,55 @@
"use strict";
const { getModel } = require( '../../../lib/Models' );
const Model = getModel('loads');
const vehicle_projection = ['background_tracking','status','last_location_lat',
'last_location_lng','last_location_geo','last_location_time']
const user_projection = ['first_name','last_name','middle_name']
const company_projection = ["company_name"]
const populate_list = [
{path:'posted_by',select: user_projection },
{path:'vehicle',select: vehicle_projection },
{path:'company',select: company_projection },
'posted_by_name',
'categories'
];
const getById = async(req, res) => {
try{
const elementId = req.params.id;
const select = [
"categories",
"truck_type",
"published_date",
"createdAt",
"status",
"load_status",
"weight",
"est_loading_date",
"est_unloading_date",
"origin.city",
"origin_geo",
"origin.state",
"destination.city",
"destination.state",
"destination_geo",
];
// const load = await Model.findOne( { _id : elementId , load_status : "Transit" } , select ).populate( populate_list );
const load = await Model.findById( elementId , select ).populate( populate_list );
if( load ){
return res.send( load );
}else{
return res.status(400).send({
error : `Load [${elementId}] not found!`
})
}
}catch(error){
console.error( error );
return res.status( 500 ).send({ error });
}
};
module.exports = { getById };

View File

@@ -0,0 +1,7 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
module.exports = router;

View File

@@ -0,0 +1,32 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination , getPage } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/loads.model.js` );
const categoriesModel = require( `${ROOT_PATH}/${MODELS_PATH}/product-categories.model.js` );
const populate_list = ['categories'];
const generic = new GenericHandler( Model, null, populate_list );
const getList = async(req, res) => {
const filter = { status : "Published" };
const select = [
"categories",
"truck_type",
"published_date",
"createdAt",
"status",
"weight",
"est_loading_date",
"est_unloading_date",
"origin.city",
"origin.state",
"destination.city",
"destination.state",
];
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements, filter, select );
res.send( retVal );
};
module.exports = { getList };

View File

@@ -0,0 +1,8 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/published', services.getListPublished);
router.get('/location', services.getListLocations);
module.exports = router;

View File

@@ -0,0 +1,71 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination , getPage } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/vehicles.model.js` );
const categoriesModel = require( `${ROOT_PATH}/${MODELS_PATH}/product-categories.model.js` );
const populate_list = ['categories'];
const generic = new GenericHandler( Model, null, populate_list );
const getListPublished = async(req, res) => {
const filter = { is_available : true };
const select = [
"city",
"state",
"truck_type",
"tyre_type",
"destino",
"available_in",
"available_date",
"createdAt",
"updatedAt",
"published_date",
"status",
"is_available",
"categories"
];
const { elements } = getPagination( req.query );
const page = 0;// No pagination allowed to this endpoint
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const retVal = await generic.getList(page , query_elements, filter, select );
res.send( retVal );
};
const getListLocations = async(req, res) => {
const filter = { status : "Free" };
const select = [
"last_location_geo",
"driver",
"updatedAt",
"status",
"categories"
];
const { elements } = getPagination( req.query );
const page = 0;// No pagination allowed to this endpoint
let query_elements;
if( elements >= 100 ){
query_elements = 100;// Never return more than 100 elements
}else{
query_elements = elements;
}
const objQuery = await generic.getListQuery( page , query_elements, filter, select );
const data = await objQuery.query.sort("field -updatedAt").populate("categories").exec();
const retVal = {
total : objQuery.total,
limit : objQuery.limit,
skip : objQuery.skip,
data : data
};
res.send( retVal );
};
module.exports = { getListPublished, getListLocations };

View File

@@ -0,0 +1,9 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.get('/', services.getList);
router.get('/find', services.findList);
router.get('/:id', services.getById);
module.exports = router;

View File

@@ -0,0 +1,33 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
const { getPagination , getPage } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const Model = require( `${ROOT_PATH}/${MODELS_PATH}/states.model.js` );
const generic = new GenericHandler( Model, "state_name" );
const getList = async(req, res) => {
const { page , elements } = getPagination( req.query );
const retVal = await generic.getList(page , elements);
res.send( retVal );
};
const findList = async(req, res) => {
const findString = req.query.regex || null;
const { page , elements } = getPagination( req.query );
let retVal;
if( findString ){
retVal = await generic.findList( findString, page, elements );
}else{
retVal = await generic.getList(page , elements);
}
res.send( retVal );
};
const getById = async(req, res) => {
const id=req.params.id;
const retVal = await generic.getById( id );
res.send( retVal );
};
module.exports = { getList , findList , getById };

View File

@@ -0,0 +1,8 @@
'use strict';
const router = require('express').Router();
const services= require('./services.js');
router.post('/apitest', services.postTest);
router.get('/version', services.getVersion);
module.exports = router;

View File

@@ -0,0 +1,20 @@
"use strict";
const { ROOT_PATH, LIB_PATH, API_CONFIG } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const postTest = async(req, res) => {
res.send({
msg:"Hello world!",
data:{
apiQuery:req.query,
apiParams:req.params.params,
body:req.body,
}
});
};
const getVersion = async(req, res) => {
res.send( apiConfig.version );
};
module.exports = { postTest , getVersion };

View File

@@ -0,0 +1,48 @@
{
"authentication": {
"pwdSecret":"Nx2g_IWo2Zt_LS$+",
"jwtSecret":"9o3BBz0EsrwXliwEJ/SFuywZoN8=",
"jwtTimeout":24,
"jwtRenewalTimeout":720,
"tokenSecret":"9Z'jMt|(h_f(&/S+zv.K",
"jwtOptions": {
"header": {
"typ": "access"
},
"audience": "https://www.etaviaporte.com",
"issuer": "etaviaporte",
"algorithm": "HS256",
"expiresIn": "1d"
}
},
"version" : {
"version" : "1.1.1",
"name": "ETA Beta",
"date":"03/2024"
},
"S3" : {
"accessKeyId": "AKIAXTQEUF6MLCHTUIKW",
"secretAccessKey": "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",
"bucket": "enruta",
"load_attachments_key":"loadattachments",
"news_key":"news",
"region": "us-west-1"
},
"sendgrid" : {
"HOST": "smtp.sendgrid.net",
"PORT": "465",
"username": "apikey",
"API_KEY": "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",
"FROM": "noreply@etaviaporte.com"
},
"email_standalone" : {
"host": "smtp.hostinger.com",
"port": "465",
"secure": true,
"auth": {
"user": "noreply@etaviaporte.com",
"pass": "-)WJt[oP~P$`76Q4"
}
},
"mongodb": "mongodb+srv://enruta_admin:NeptFx4RUZG8OsfA@enruta.vwofshy.mongodb.net/enrutaviaporte?retryWrites=true&w=majority"
}

View File

@@ -0,0 +1,48 @@
{
"authentication": {
"pwdSecret":"Nx2g_IWo2Zt_LS$+",
"jwtSecret":"9o3BBz0EsrwXliwEJ/SFuywZoN8=",
"jwtTimeout":24,
"jwtRenewalTimeout":720,
"tokenSecret":"9Z'jMt|(h_f(&/S+zv.K",
"jwtOptions": {
"header": {
"typ": "access"
},
"audience": "https://www.etaviaporte.com",
"issuer": "etaviaporte",
"algorithm": "HS256",
"expiresIn": "1d"
}
},
"version" : {
"version" : "1.1.1",
"name": "ETA Beta",
"date":"03/2024"
},
"S3" : {
"accessKeyId": "AKIAXTQEUF6MLCHTUIKW",
"secretAccessKey": "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",
"bucket": "enruta",
"load_attachments_key":"loadattachments",
"news_key":"news",
"region": "us-west-1"
},
"sendgrid" : {
"HOST": "smtp.sendgrid.net",
"PORT": "465",
"username": "apikey",
"API_KEY": "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",
"FROM": "noreply@etaviaporte.com"
},
"email_standalone" : {
"host": "smtp.hostinger.com",
"port": "465",
"secure": true,
"auth": {
"user": "noreply@etaviaporte.com",
"pass": "-)WJt[oP~P$`76Q4"
}
},
"mongodb": "mongodb://localhost/etaviaporte?retryWrites=true&w=majority"
}

73
v1/src/index.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
require('dotenv').config();
const { ROOT_PATH, LIB_PATH, API_CONFIG } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const apps = require('./apps');
const express = require('express');
const cors = require('cors');
const compression = require('compression');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');
const fileUpload = require('express-fileupload');
const middlewares = require( `${ROOT_PATH}/${LIB_PATH}/Middlewares.js` );
const mongoose = require('mongoose');
mongoose.connect(
apiConfig.mongodb,
{ useNewUrlParser: true }
).then( ( val ) => {
console.log( `MongoDB Connected : ${ apiConfig.mongodb }` );
});//catch throw error so service stops!
const app = express();
const serverPort = process.env.SERVER_PORT || 3000;
app.use( middlewares.Auth );
app.use(
fileUpload({
limits: { fileSize: 4 * 1024 * 1024 },
abortOnLimit: true,
limitHandler: (req,res,next) => {
req.limitSize = true;
},
})
);
app.use((req, res, next) => {
if (req.limitSize) {
res.status(413).send({message:"File size limit has been reached",status:"PAYLOAD_TOO_LARGE"});
}else{
next()
}
});
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(morgan('dev'));
app.use(helmet({
crossOriginResourcePolicy: false
}));
app.use(compression());
app.use(cors({
origin: '*',
methods: [
'GET',
'POST',
'PATCH',
'PUT',
'DELETE'
],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use( middlewares.errorJSON );
app.use( apps );
app.use( middlewares.error404 );
app.listen( serverPort , function(err){
if( !err ){
console.log('API listen on port', serverPort );
}else{
console.log( err );
}
});

View File

@@ -0,0 +1,123 @@
'user strict';
const { ROOT_PATH, API_CONFIG, MODELS_PATH, LIB_PATH } = process.env;
const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` );
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const { toSha256 } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const UserModel = getModel('users');
const companiesModels = getModel('companies');
const pwd_secret = apiConfig.authentication.pwdSecret;
async function create_account( email, password ){
let safe_password = toSha256( password + pwd_secret );
const user = new UserModel({
email,
password : safe_password,
job_role : 'owner',//Always a new user created from signup is owner
isVerified : true,
has_password : true
});
await user.save();
// Create user code
const id = "" + user._id;
const employee_id = "E-" + id.substring( 0 , 6 );
await UserModel.findByIdAndUpdate( id , {
employee_id
});
}
async function reset_password( email, password ){
let safe_password = toSha256( password + pwd_secret );
const user = await UserModel.findOne({ email });
user.password = safe_password;
user.isVerified = true;
user.has_password = true;
await user.save();
return user;
}
async function already_exists( email ){
const user = await UserModel.findOne( { email } );
if( !user ){
return false;
}else{
return true;
}
}
async function verify_driver_account( email ){
const user = await UserModel.findOne( { email } );
const retVal = {
has_account:false,
isVerified:false,
has_password:false
};
if( !user ){
retVal.has_account = false;
retVal.isVerified = false;
retVal.has_password = false;
}else{
retVal.has_account = true;
retVal.isVerified = user.isVerified;
retVal.has_password = ( !user.password )? false : true;
}
return retVal;
}
async function login( email , password ){
let safe_password = toSha256( password + pwd_secret );
const user = await UserModel.findOne({
email , password : safe_password
},{ password : 0 , session_token : 0 , session_token_exp : 0 }).populate('company');
return user;
}
async function login_with_session_token( session_token ){
const user = await UserModel.findOne({
session_token,
session_token_exp : { $gte: new Date() }
},{ password : 0 , session_token : 0 , session_token_exp : 0 }).populate('company');
return user;
}
async function complete_register( userId , data ){
let {
company_type
} = data;
let permissions;
if( company_type.toLowerCase() === "shipper" ){
company_type = "Shipper";
permissions = "role_shipper";
}else if( company_type.toLowerCase() === "carrier" ){
company_type = "Carrier";
permissions = "role_carrier";
}else{
throw "Invalid company type";
}
data.company_type = company_type;
const user = await UserModel.findById( userId , { password : 0 , session_token : 0 , session_token_exp : 0 } );
if( user.company ){
throw "User already register";
}
const company = new companiesModels( data );
await company.save();
user.company = company;
user.job_role = "owner";
user.permissions = permissions;
user.isVerified = true;
await user.save();
return company;
}
module.exports = { create_account, already_exists, verify_driver_account, login, login_with_session_token, reset_password, complete_register };

View File

@@ -0,0 +1,111 @@
"use strict";
const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env;
async function getPage( page, elements, Model, filter=null, projection=null){
const skip = elements * page;
const total = await Model.count( filter );
const list = await Model.find( filter , projection, { skip : skip , limit : elements } );
return {
total : total,
limit : elements,
skip : skip,
data : list
}
}
async function getPageQuery(page, elements, Model, filter=null, projection=null){
const skip = elements * page;
const total = await Model.count( filter );
return {
query : Model.find( filter , projection, { skip : skip , limit : elements } ),
total : total,
skip : skip
};
}
class GenericHandler{
constructor( Model, search_param=null, populate_list=null, populate_select=null ) {
this.Model = Model;
this.search_param = search_param || null;
this.populate_list = populate_list || [];
this.populate_select = populate_select || null;
}
async populateQuery( query ){
if( this.populate_list.length > 0 ){
query.populate( this.populate_list );
}
else
if( this.populate_select != null ){
for( const [key,value] of Object.entries(this.populate_select) ){
query.populate( key , value );
}
}
return await query.exec();
}
async getList( page, elements, filter=null, projection=null, sort=null ){
const { query , total, skip } = await getPageQuery( page , elements, this.Model, filter, projection );
if( sort ){
query.sort( sort );
}
const list = await this.populateQuery( query );
return {
total : total,
limit : elements,
skip : skip,
data : list
};
}
/**
* Not populated query.
*
* Typical usage:
* @code{js}
* const queryObj = await generic.getListQuery(page, elements, filter, projection );
* queryObj.query.SOME_ACTIONS();
* const data = await generic.populateQuery( queryObj.query );
* @endcode
* @param {*} page
* @param {*} elements
* @param {*} filter
* @param {*} projection
* @returns { total , limit, skip, query}
*/
async getListQuery( page, elements , filter=null, projection=null, sort=null ){
const { query , total, skip } = await getPageQuery( page , elements, this.Model, filter, projection );
if( sort ){
query.sort( sort );
}
return {
total : total,
limit : elements,
skip : skip,
query : query
};
}
async findList( find_string, page, elements, projection=null ){
if( !this.search_param ){
throw new Error( "No search parameter setted up" );
}
const search_param = this.search_param;
const re = new RegExp( find_string );
const filter = {};
filter[ search_param ] = { $regex: re, $options: 'i' };
return await this.getList( page, elements, filter, projection );
}
async getById( id, projection=null ){
const query = Model.findById( id, projection );
return await this.populateQuery( query );
}
};
module.exports = { getPage, getPageQuery, GenericHandler };

View File

@@ -0,0 +1,85 @@
'user strict';
const { ROOT_PATH, API_CONFIG } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const nodemailer = require("nodemailer");
const SendGrid = require("nodemailer-sendgrid");
const SiteName = "ETA Viaporte";
const sendgridConfig = apiConfig.sendgrid;
const transporter = nodemailer.createTransport(
SendGrid({
host: sendgridConfig.HOST,
apiKey: sendgridConfig.API_KEY
})
);
async function sendMailTemplate( templateId, receiver, subject, content ){
/**TODO: Remove in production */
const default_mail_list = [
{pattern:"testing@etaviaporte.com",redirect:"testing@etaviaporte.com"},
{pattern:"alex@etaviaporte.com",redirect:"alexandro_uribe@outlook.com"},
{pattern:"pablo@etaviaporte.com",redirect:"josepablo134@gmail.com"}
];
for( let i=0; i< default_mail_list.length; i++ ){
if( receiver.indexOf( default_mail_list[i].pattern ) >= 0 ){
receiver = default_mail_list[i].redirect;
break;/** Set only the first match */
}
}
return await transporter.sendMail({
from: sendgridConfig.FROM,
to: receiver,
subject: subject,
templateId: templateId,
dynamic_template_data: content
});
}
async function AccountVerifyEmail( receiver , content ){
const templateId = "d-e9b7966303694964a64b6e4954e9715d";
const subject = "[ETA] Account Verification";
const content_to_send = {
project_name: SiteName,
user_name: content.user_name,
user_email: receiver,
OTP : content.OTP
};
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
}
async function AccountConfirmed( receiver, content ){
const templateId = "d-4daaab1b85d443ceba38826f606e9931";
const subject = "[ETA] Welcome to ETA";
const content_to_send = {
user_name: content.user_name,
};
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
}
async function AccountPwdResetEmail( receiver, content ){
const templateId = "d-e9b7966303694964a64b6e4954e9715d";
const subject = "[ETA] Password Reset";
const content_to_send = {
project_name: SiteName,
user_name: content.user_name,
user_email: receiver,
OTP : content.OTP
};
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
}
async function ContactEmail( receiver, content ){
const templateId = "d-1090dda1091442f3a75ee8ab39ad0f10";
const subject = "[ETA] Contact Email";
const content_to_send = {
project_name: SiteName,
user_name: content.name,
user_email: receiver
};
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
}
//ContactEmail( "josepablo134@gmail.com", { email : "josepablo134@gmail.com", name:"Josepablo C.", message: "This is an example" } ).then().catch();
module.exports = { AccountVerifyEmail, AccountConfirmed, AccountPwdResetEmail, ContactEmail };

View File

@@ -0,0 +1,24 @@
'user strict';
const { ROOT_PATH, API_CONFIG } = process.env;
const nodemailer = require("nodemailer");
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const transporter = nodemailer.createTransport(
apiConfig.email_standalone
);
async function StandAloneContactEmail( content ){
const default_from = apiConfig.email_standalone.auth.user;
const receiver = "support@etaviaporte.com";
const {name, email, message } = content;
return await transporter.sendMail({
from: `${name} <${default_from}>`,
to: receiver,
subject: "Contact Email From Landing Page",
text: `\n\n The following is an email from : ${email}\n\n\n` + message
});
}
//StandAloneContactEmail( { email : "josepablo134@gmail.com", name:"Josepablo C.", message: "This is an example" } ).then().catch();
module.exports = { StandAloneContactEmail };

View File

@@ -0,0 +1,50 @@
'user strict';
const { ROOT_PATH, HANDLERS_PATH, MODELS_PATH, API_CONFIG } = process.env;
const { StandAloneContactEmail } = require('./StandAlone.handler');
const { AccountVerifyEmail, AccountConfirmed, AccountPwdResetEmail, ContactEmail } = require('./SendGrid.handler');
const EMAIL_EVENTS={
ACCOUNT_VERIFY:1,
ACCOUNT_CONFIRMED:2,
ACCOUNT_PWD_RESET:3,
CONTACT_EMAIL:4,
}
/**
* Send an email according to the event.
* @param eventId : string
* @param email_content : { string receiver, {*} content }
* @returns
*/
async function emailEvent( eventId, receiver , content ){
switch( eventId ){
case EMAIL_EVENTS.ACCOUNT_VERIFY:
{
return await AccountVerifyEmail( receiver, content );
}
break;
case EMAIL_EVENTS.ACCOUNT_CONFIRMED:
{
return await AccountConfirmed( receiver, content );
}
break;
case EMAIL_EVENTS.ACCOUNT_PWD_RESET:
{
return await AccountPwdResetEmail( receiver, content );
}
break;
case EMAIL_EVENTS.CONTACT_EMAIL:
{
await StandAloneContactEmail( content );
return await ContactEmail( receiver, content );
}
break;
default:
{
throw new Error(`Email event not defined ${eventId}`);
}
break;
}
}
module.exports = { emailEvent , EMAIL_EVENTS };

View File

@@ -0,0 +1,52 @@
'user strict';
const { getModel } = require( '../Models' );
const vehiclesModel = require('../Models/vehicles.model');
const proposalsModel = getModel('proposals');
const loadsModel = getModel('loads');
const usersModel = getModel('users');
const companiesModel = getModel('companies');
/**
* When the proposal is accepted then the load should be updated to have the
* @param {*} id
* @param {*} newProposalData
* @returns
*/
async function onPatchEvent( id , newProposalData ){
const proposal = await proposalsModel.findById( id );
if( !newProposalData.is_accepted ){
/// Update Proposal:
/// Remove shipper
await proposalsModel.findByIdAndUpdate( id , {
shipper : null
} );
/// Update Load:
/// Remove carrier, driver and vehicle
await loadsModel.findByIdAndUpdate( proposal.load, {
carrier : null,
driver : null,
vehicle : null,
} );
}else{
const shipper_user = await usersModel.findById( proposal.accepted_by );
const shipper = await companiesModel.findById( shipper_user.company );
const vehicle = await vehiclesModel.findById( proposal.vehicle );
/// Update Proposal:
/// Adding shipper to proposal
await proposalsModel.findByIdAndUpdate( id , {
shipper : shipper.id
} );
/// Update Load:
/// Add carrier, driver and vehicle
await loadsModel.findByIdAndUpdate( proposal.load, {
carrier : proposal.carrier,
driver : vehicle.driver,
vehicle : proposal.vehicle,
} );
}
}
module.exports = { onPatchEvent };

View File

@@ -0,0 +1,187 @@
'user strict';
const { ROOT_PATH, HANDLERS_PATH, LIB_PATH } = process.env;
const { getModel } = require( '../Models' );
const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` );
const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
const usersModel = getModel('users');
const companiesModel = getModel('companies');
const populate_list = ['company','branch','vehicle','active_load','categories'];
const generic = new GenericHandler( usersModel, "first_name", populate_list );
async function getUserById( id , filter ){
if( filter ){
filter._id = id;
const user = await usersModel.findOne( filter , { password : 0 , session_token : 0 , session_token_exp : 0 } );
return user;
}else{
return await usersModel.findById( id , { password : 0 , session_token : 0 , session_token_exp : 0 } ).populate('company');
}
}
function getAndFilterList( query ){
const filter_list = [];
const { email, permissions, gender, job_role, employee_id, company, branch, vehicle, active_load, categories } = query;
if( email ){ filter_list.push( { email } ); }
if( permissions ){ filter_list.push( { permissions } ); }
if( gender ){ filter_list.push( { gender } ); }
if( job_role ){ filter_list.push( { job_role } ); }
if( employee_id ){ filter_list.push( { employee_id } ); }
if( company ){ filter_list.push( { company } ); }
if( branch ){ filter_list.push( { branch } ); }
if( vehicle ){ filter_list.push( { vehicle } ); }
if( active_load ){ filter_list.push( { active_load } ); }
if( categories ){ filter_list.push( { categories } ); }
if( filter_list.length == 0 ){
return null;
}
return filter_list;
}
async function findUsers( query ){
const filter = { "is_hidden" : false , "is_deleted" : false };
const { page, elements } = getPagination( query );
const andFilterList = getAndFilterList( query );
if( andFilterList ){
filter.$and = andFilterList;
}
let search_param;
let search_value;
if( query.first_name ){
search_param = "first_name";
search_value = query.first_name;
}
else if( query.last_name ){
search_param = "last_name";
search_value = query.last_name;
}
else if( query.middle_name ){
search_param = "middle_name";
search_value = query.middle_name;
}
else if( query.email ){
search_param = "email";
search_value = query.email;
}
else if( query.phone ){
search_param = "phone";
search_value = query.phone;
}
else if( query.phone2 ){
search_param = "phone2";
search_value = query.phone2;
}
if( search_param ){
const re = new RegExp( search_value );
filter[ search_param ] = { $regex: re, $options: 'i' };
}
const queryVal = await generic.getList(page , elements, filter, { password : 0 , session_token : 0 , session_token_exp : 0 } );
return {
total : queryVal.total,
limit : queryVal.limit,
skip : queryVal.skip,
data : queryVal.data
};
}
function clean_user_data( data , company ){
/// Avoid modifying sensitive fields.
if( data.password ){ delete data.password; }
if( data.company ){ delete data.company; }
if( data.job_role ){
/// System can only create manager,driver or staff.
if( (data.job_role !== "manager") && (data.job_role !== "driver") && (data.job_role !== "staff") ){
data.job_role = "staff";
}
}
if( data.permissions ){ delete data.permissions; }
if( company ){
if( company.company_type === 'Shipper' ){
data.permissions = "role_shipper";
}else
if( company.company_type === 'Carrier' ){
data.permissions = "role_carrier";
}
}
if( data.session_token ){ delete data.session_token; }
if( data.session_token_exp ){ delete data.session_token_exp; }
if( data.is_deleted ){ delete data.is_deleted; }
return data;
}
async function patchUserData( id , data ){
/// Avoid modifying sensitive fields.
data = clean_user_data( data , null );
const user = await usersModel.findById( id , { password : 0 , session_token : 0 , session_token_exp : 0 } );
if( (data.email) && (data.email !== user.email) ){
const user_already_exists = await usersModel.findOne({ email : data.email });
if( user_already_exists ){
throw "email already exists, please choose other";
}
/// Changing the email requires a password recovery in order to verify the email!!
data.password = "reset your password please";
}else{
delete data.email;
}
await usersModel.findByIdAndUpdate( id , data );
return await usersModel.findById( id , { password : 0 , session_token : 0 , session_token_exp : 0 } ).populate('company');
}
async function createUserWithinCompany( companyId , data ){
const company = await companiesModel.findById( companyId );
/// Avoid modifying sensitive fields.
data = clean_user_data( data , company );
data.company = companyId;
if( data.email ){
const user_already_exists = await usersModel.findOne({ email : data.email });
if( user_already_exists ){
throw "email already exists";
}
}else{
throw "email is required";
}
data.isVerified = false;
const user = new usersModel( data );
await user.save();
// Create user code
const id = "" + user._id;
const employee_id = "E-" + id.substring( 0 , 6 );
await usersModel.findByIdAndUpdate( id , {
employee_id
});
user.employee_id = employee_id;
return user;
}
async function deleteUserWithinCompany( manager_id , user_to_remove_id ){
if( manager_id === user_to_remove_id ){ throw "Manager can not remove it self"; }
const manager = await usersModel.findById( manager_id ).populate( "company" );
const company = manager.company;
if( !manager ){ throw "Invalid manager or owner"; }
if( !company ){ throw "Invalid company"; }
const user = await usersModel.findOne( {
_id : user_to_remove_id ,
company : manager.company.id
} );
if( !user ){ throw "User is invalid"; }
await usersModel.findOneAndDelete( {
_id : user_to_remove_id ,
company : manager.company.id
} );
return user;
}
module.exports = { getUserById , findUsers , patchUserData , createUserWithinCompany , deleteUserWithinCompany };

87
v1/src/lib/Middlewares.js Normal file
View File

@@ -0,0 +1,87 @@
'use strict';
/**
* HASH
*****************************************************
* DEPENDENCIES
*****************************************************
* Based on Express Framework
* System
*****************************************************
* PUBLIC METHODS
*****************************************************
* Auth( req, res, next)
* Extract JWT or BasicAuth data
* errorJSON( error , request , response , next )
* Generate error response on bad JSON format
* error404( request , response , next )
* Generate error 404 response
* apiKey( request , response , next )
* Generate error on invalid apikey
**/
/// Extract JWT or BasicAuth
function Auth( req, res , next ){
///
/// Try to extract the authorization data from headers
///
let auth;
if( req.headers.hasOwnProperty( "authorization" ) ){
auth = req.headers.authorization;
auth = auth.split(" ")[1];
if( !auth ){ console.log( "NO HEADER AUTH available" ); return next(); }
//console.log( auth );
/// Try BasicAuth {
try{
let ba = Buffer.from( auth , 'base64' ).toString()
//const [user,pass] = ba.split(':');
ba = ba.split(':');
if( ba.length == 2 ){
req.basicAuth = { user : ba[0] , password : ba[1] };
}
}catch(error){
console.log("MIDDLEWARE_AUTH_ERR_BA",error);
}
/// Try BasicAuth }
}else if( req.query.access_token ){
auth = req.query.access_token;
if( !auth ){ console.log( "NO QUERY AUTH available" ); return next(); }
}
if( auth ){
/// Try JWT {
try{
let jwt = auth.split(".");
if( jwt.length == 3 ){
req.JWT = {};
req.JWT.raw = auth;
}
}catch( error ){
console.log("MIDDLEWARE_AUTH_ERR_JWT",error);
}
/// Try JWT }
}
next();
}
function errorJSON( error , request , response , next ){
console.log(error);
if( error !== null ){
/// For body-parser errors
if( error instanceof SyntaxError && error.status === 400 && 'body' in error ){
return response.status(400).json({ error : 'Invalid json' , code : 400 });
}
/// For any error
return response.status(500).send( { error: "Internal server error" , code : 500 } );
}else{
return next();
}
}
function error404( request , response , next ){
return response.status(404).send( { error : "Page not found", code : 404 } );
}
module.exports = {
Auth,
errorJSON,
error404,
};

105
v1/src/lib/Misc.js Normal file
View File

@@ -0,0 +1,105 @@
"use strict";
const { ROOT_PATH, API_CONFIG } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const crypto = require('crypto');
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const s3Client = new S3Client({
region : apiConfig.S3.region,
credentials : {
accessKeyId : apiConfig.S3.accessKeyId,
secretAccessKey : apiConfig.S3.secretAccessKey
}
});
const secret = apiConfig.authentication.jwtSecret;
const tokenSecret = apiConfig.authentication.tokenSecret;
/**
* Convert string to sha256 string in hex
* @param {*} text
* @returns
*/
function toSha256( text ){
return crypto.createHmac( "sha256" , "" ).update( text ).digest( 'hex' );
}
/**
* Generate string with fixed length with random content.
* Length is limited to 64 characters.
* @param {*} text
* @returns
*/
function genKey( len = 5 , key="" ){
if( len >= 64 ){
throw "invalid key len";
}
const shacode = toSha256( key + new Date() + tokenSecret );
const otp_hex = shacode.slice(0 , len );
const otp_dec = Number.parseInt( otp_hex , 16 );
return ""+otp_dec;
}
function getPagination( query ){
let limit = {
page : 0,
elements : 10
};
if( query.page ){
limit.page = parseInt( query.page ) || 0;
if( limit.page < 0 ){
limit.page = 0;
}
}
if( query.elements ){
limit.elements = parseInt( query.elements ) || 10;
/** Safe pagination limit */
if( limit.elements > 1000 ){
limit.elements = 1000;
}
else if( limit.elements < 0 ){
limit.elements = 10;
}
}
return limit;
}
async function getPage( page, elements, model, filter=null, projection=null){
const skip = elements * page;
const total = await model.count( filter );
const list = await model.find( filter , projection, { skip : skip , limit : elements } );
return {
total : total,
limit : elements,
skip : skip,
data : list
}
}
async function queryPage(page, elements, model, filter=null, projection=null){
const skip = elements * page;
const total = await model.count( filter );
return {
query : model.find( filter , projection, { skip : skip , limit : elements } ),
total : total,
skip : skip
};
}
async function downloadFile( bucket, key, obj_id ){
const params = {
Bucket: bucket,
Key : `${key}/${obj_id}`
};
const s3resp = await s3Client.send( new GetObjectCommand( params ) );
const chunks = []
for await (const chunk of s3resp.Body) {
chunks.push(chunk)
}
const body = Buffer.concat(chunks);
s3resp.Body = body;
return s3resp;
}
module.exports = { genKey , toSha256, getPagination, getPage, queryPage, downloadFile};

View File

@@ -0,0 +1,16 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
categories: [{ type: Schema.Types.ObjectId, ref: 'productcategories' }],
company: { type: Schema.Types.ObjectId, ref: 'companies' },
branch_name: { type: String },
phone: { type: String },
city: { type: String },
state: { type: String },
truck_type: [{ type: String }],
description:{type: String},
address : { type: String }
});
module.exports = mongoose.model( "branches", schema );

View File

@@ -0,0 +1,33 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
company: { type: Schema.Types.ObjectId, ref: 'companies', required: true},
client: { type: String, required: true },
material: { type: String },
origin: { type: String },
destination: { type: String },
truck_type: { type: String },
num_tons: { type: Number },
price_per_ton: { type: Number },
tonnage: { type: String },
pickup_distance: { type: Number },
delivery_distance: { type: Number },
warehouse_distance: { type: Number },
total_km_travel: { type: Number },
cost_per_liter: { type: Number },
fuel_price_per_liter: { type: Number },
other_fuel_expenses: { type: Number },
total_fuel_consumed: { type: Number },
total_cost_fuel: { type: Number },
driver_salary: { type: Number },
accomadation_allowance: { type: Number },
other_administrative_expenses: { type: Number },
total_before_tax: { type: Number },
total_utility_per_km: { type: Number },
total_profit: { type: Number },
profit_percentage: { type: Number },
total_administrative_expenses: { type: Number }
});
module.exports = mongoose.model( "budgets", schema );

View File

@@ -0,0 +1,11 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
country_name: { type: String },
country_code: { type: String },
state_name: { type: String },
city_name: { type: String, required: true }
});
module.exports = mongoose.model( "cities", schema );

View File

@@ -0,0 +1,65 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const meta_data = new Schema({
meta_group: { type: String },
meta_key: { type: String },
meta_value: { type: String },
});
const company_contacts = new Schema({
meta_key: { type: String },
meta_value: { type: String },
});
const address = new Schema({
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 schema = new Schema({
company_serial_number: { type: String },
company_code: { type: String },
is_company: { type: String }, //1000
company_name: { type: String, required: true },
company_legal_name: { type: String },
company_description: { type: String },
rfc: { type: String },
company_type: { type: String, enum : [ 'Shipper', 'Carrier' ] },
is_broker: { type: Boolean, default: false },
membership: { type: String },
membership_start_at: { type: Date },
meta_data: [meta_data],
categories: [{ type: Schema.Types.ObjectId, ref: 'productcategories' }],
products: { type: Schema.Types.ObjectId, ref: 'products' },
branches: [{ type: Schema.Types.ObjectId, ref: 'branches' }],
company_city: [{ type: String }],
company_state: [{ type: String }],
truck_type: [{ 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 },
is_hidden: { type: Boolean, default: false },
createdAt: { type : Date, required : true, default : () => { return Date.now(); } }
});
module.exports = mongoose.model( "companies", schema );

View File

@@ -0,0 +1,10 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
country_name: { type: String, required: true },
country_code: { type: String, required: true },
phone_code: { type: String, required: true }
});
module.exports = mongoose.model( "countries", schema );

View File

@@ -0,0 +1,73 @@
"use strict";
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 countries = require('./countries.model.js');
const load_attachments = require('./load-attachments.model.js');
const loads = require('./loads.model.js');
const mailer = require('./mailer.model.js');
const memberships = require('./memberships.model.js');
const meta_data = require('./meta-data.model.js');
const meta_groups = require('./meta-groups.model.js');
const news = require('./news.model.js');
const orders = require('./orders.model.js');
const product_categories = require('./product-categories.model.js');
const products = require('./products.model.js');
const proposals = require('./proposals.model.js');
const states = require('./states.model.js');
const trackings = require('./trackings.model.js');
const users = require('./users.model.js');
const vehicles = require('./vehicles.model.js');
function getModel( name ){
switch( name ){
case 'branches':
return branches;
case 'budgets':
return budgets;
case 'cities':
return cities;
case 'companies':
return companies;
case 'countries':
return countries;
case 'load_attachments':
return load_attachments;
case 'loads':
return loads;
case 'mailer':
return mailer;
case 'memberships':
return memberships;
case 'meta_data':
return meta_data;
case 'meta_groups':
return meta_groups;
case 'news':
return news;
case 'orders':
return orders;
case 'product_categories':
return product_categories;
case 'products':
return products;
case 'proposals':
return proposals;
case 'states':
return states;
case 'trackings':
return trackings;
case 'users':
return users;
case 'vehicles':
return vehicles;
default:
return null;
}
}
module.exports = {
getModel
};

View File

@@ -0,0 +1,19 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
status: { type: String, default: 'Draft', enum: ['Draft', 'Done'] },/*Once in Done state, no changes are allowed.*/
updatedAt: {
type: Date,
default : () => Date.now()
},
type: { type: String, enum: ['Loading', 'Downloading'], required : true },
company: { type: Schema.Types.ObjectId, ref: 'companies', required: true }, //shipper
carrier: { type: Schema.Types.ObjectId, ref: 'companies', required: true }, // carrier
load: { type: Schema.Types.ObjectId, ref: 'loads', required: true },
author: { type: Schema.Types.ObjectId, ref: 'users', required: true },
doneAt: { type: Date }
});
module.exports = mongoose.model( "loadattachments", schema );

View File

@@ -0,0 +1,91 @@
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({
shipment_code: { type: String },
company: { type: Schema.Types.ObjectId, ref: 'companies', required: true }, //shipper
carrier: { type: Schema.Types.ObjectId, ref: 'companies' }, // carrier
vehicle: { type: Schema.Types.ObjectId, ref: 'vehicles' },
driver: { type: Schema.Types.ObjectId, ref: 'users' },
posted_by: { type: Schema.Types.ObjectId, ref: 'users' }, // shipper
posted_by_name: { type: String }, // search purpose
bidder: { type: Schema.Types.ObjectId, ref: 'users' },
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 },
// 1. Posted Shipments (Posted)
// 2. Shipments Loading (Loading)
// 3. Enroute Shipments (In Transit)
// 4. Shipments in Unloading (Unloading)
// 5. Shipments pending finding truck
status: { type: String, default: 'Draft', enum: ['Draft', 'Published', 'Completed', 'Closed'] },
load_status: { type: String, enum: ['Published', 'Loading', 'Transit', 'Downloading', 'Delivered'] },
contract_start_date: { type: Date },
contract_end_date: { type: Date },
est_loading_date: { type: Date },
est_unloading_date: { type: Date },
published_date: { type: Date },
loaded_date: { type: Date },
transit_date: { type: Date },
delivered_date: { type: Date },
load_status_updated: { type: Date, default: () => Date.now() },
notes: { type: String },
payment_term: { type: String },
terms_and_conditions: { type: String },
createdAt: { type : Date, required : true, default : () => { return Date.now(); } }
});
module.exports = mongoose.model( "loads", schema );

View File

@@ -0,0 +1,8 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
text: { type: String, required: true }
});
module.exports = mongoose.model( "mailer", schema );

View File

@@ -0,0 +1,10 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
name: { type: String, required: true }, // free, pro
price: { type: Number },
validity : { type: Number }, // number 0f days
});
module.exports = mongoose.model( "memberships", schema );

View File

@@ -0,0 +1,10 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
meta_group: { type: Schema.Types.ObjectId, ref: 'metagroups'},// settings, terms, collaborator
meta_key: { type: String, required: true }, // collaborator
meta_value: { type: String }
});
module.exports = mongoose.model( "metadatas", schema );

View File

@@ -0,0 +1,11 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
group_label: { type: String, required: true },
group_key: { type: String, required: true },
group_field_type: { type: String,default:'text' }, // text, textarea, html, select
group_options: [{ type: String }]
});
module.exports = mongoose.model( "metagroups", schema );

View File

@@ -0,0 +1,26 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const attachFile = new Schema({
file:{ type: String },
originalname:{ type: String },
mimetype:{ type: String },
focus_point:[{
thumbnail:{ type: String, default:"main" },
x:{ type: Number },
y:{ type: Number },
w:{ type: Number },
h:{ type: Number },
s:{ type: Number }
}]
});
const schema = new Schema({
title : { type: String, required: true },
description : { type: String, required: true },
link_text : { type: String, required: true },
link_url : { type: String, required: true },
news_image: attachFile,
});
module.exports = mongoose.model( "news", schema );

View File

@@ -0,0 +1,11 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
load: { type: Schema.Types.ObjectId, ref: 'loads' },
shipper: { type: Schema.Types.ObjectId, ref: 'companies' }, // how offers load
carrier: { type: Schema.Types.ObjectId, ref: 'companies' }, // how transport the load
vehicle: { type: Schema.Types.ObjectId, ref: 'vehicles' },
});
module.exports = mongoose.model( "orders", schema );

View File

@@ -0,0 +1,8 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
name: { type: String, required: true } // fruit, boxes, wood
});
module.exports = mongoose.model( "productcategories", schema );

View File

@@ -0,0 +1,9 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
name: { type: String, required: true },
segment: { type: String},
});
module.exports = mongoose.model( "products", schema );

View File

@@ -0,0 +1,22 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
load: { type: Schema.Types.ObjectId, ref: 'loads' },
shipper: { type: Schema.Types.ObjectId, ref: 'companies' }, // who offers load
carrier: { type: Schema.Types.ObjectId, ref: 'companies' }, // who transport the load
vehicle: { type: Schema.Types.ObjectId, ref: 'vehicles' },
bidder: { type: Schema.Types.ObjectId, ref: 'users' },
comment: { type: String },
is_withdrawn: { type: Boolean, default: false },
accepted_by: { type: Schema.Types.ObjectId, ref: 'users' },
accepted_date: { type: Date },
is_accepted: { type: Boolean, default: false },
createdAt: { type : Date, required : true, default : () => { return Date.now(); } }
});
module.exports = mongoose.model( "proposals", schema );

View File

@@ -0,0 +1,12 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
country_name: { type: String},
country_code: { type: String },
state_name: { type: String, required: true },
state_code: { type: String }
});
module.exports = mongoose.model( "states", schema );

View File

@@ -0,0 +1,12 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const schema = new Schema({
lat: { type: String },
lng: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'users' },
load: { type: Schema.Types.ObjectId, ref: 'loads' },
vehicle: { type: Schema.Types.ObjectId, ref: 'vehicles' },
});
module.exports = mongoose.model( "trackings", schema );

View File

@@ -0,0 +1,62 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const pointSchema = new Schema({
type: {
type: String,
enum: ['Point'],
required: true
},
coordinates: {
type: [Number],
required: true
}
});
const schema = new Schema({
first_name: { type: String },
last_name: { type: String },
middle_name: { type: String },
email: { type: String, unique: true, lowercase: true },
password: { type: String , maxLength : 256 },
phone: { type: String },
phone2: { type: String },
permissions: { type: String, default: 'role_admin', enum : [ 'admin', 'role_admin', 'role_shipper', 'role_carrier', 'role_driver' ] },
gender: { type: String },
address: { type: String },
dob: { type: String },
// vehicle_status: { type: String, enum: ['Free', 'Loading', 'Moving', 'Downloading'] },
job_role: { type: String, enum : [ 'admin', 'owner', 'manager', 'driver', 'staff' ] },
employee_id: { type: String }, //EM-1000-1 EM-1000-2
company: { type: Schema.Types.ObjectId, ref: 'companies' },
branch: { type: Schema.Types.ObjectId, ref: 'branches' },
vehicle: { type: Schema.Types.ObjectId, ref: 'vehicles' },
active_load: { type: Schema.Types.ObjectId, ref: 'loads' },
categories: [{ type: Schema.Types.ObjectId, ref: 'productcategories' }],
user_city: [{ type: String }],
user_state: [{ type: String }],
user_description: { type: String },
truck_type: [{ type: String }],
last_location_lat: { type: String },
last_location_lng: { type: String },
last_location_geo: { type: pointSchema },
last_location_time: { type: Date },
isVerified: { type: Boolean },
session_token : { type : String, maxLength : 256 },
session_token_exp : { type: Date },
is_hidden: { type: Boolean, default: false },
is_deleted: { type: Boolean, default: false },
deleted_at: { type: Date },
createdAt: { type : Date, required : true, default : () => { return Date.now(); } }
});
module.exports = mongoose.model( "users", schema );

View File

@@ -0,0 +1,57 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
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 }, // carrier
vehicle_code: { type: String },
vehicle_name: { type: String },
vehicle_number: { type: String }, //camion001 002 etc
circulation_serial_number: { type: String },
trailer_plate_1: { type: String },
trailer_plate_2: { type: String },
truck_type: { type: String },
tyre_type: { type: String },
city: { type: String },
state: { type: String },
background_tracking: { type: Boolean, default: false },
status: { type: String, enum: ['Free', 'Loading', 'Transit', 'Downloading'] },
categories: [{ type: Schema.Types.ObjectId, ref: 'productcategories' }],
posted_by: { type: Schema.Types.ObjectId, ref: 'users', required: true }, // carrier
published_date: { type: Date },
available_date: { type: String },
is_available: { type: Boolean, default: false },
active_load: { type: Schema.Types.ObjectId, ref: 'loads' },
load_shipper: { type: Schema.Types.ObjectId, ref: 'companies' },
available_in: { type: String },
destino: { type: String },
driver: { type: Schema.Types.ObjectId, ref: 'users' },
notes: { type: String },
last_location_lat: { type: String },
last_location_lng: { type: String },
last_location_geo: { type: pointSchema },
last_location_time: { type: Date },
createdAt: { type : Date, required : true, default : () => { return Date.now(); } }
});
module.exports = mongoose.model( "vehicles", schema );

View File

@@ -0,0 +1,29 @@
'user strict';
const { ROOT_PATH, API_CONFIG } = process.env;
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
const jwt = require('jsonwebtoken');
const jwtSecret = apiConfig.authentication.jwtSecret;
function middleware( req, res, next ){
if( req.JWT ){
req.JWT.isValid = false;
try{
req.JWT.payload = jwt.verify( req.JWT.raw, jwtSecret );
if( !req.JWT.payload ){
return res.status(401).send({error:"Unauthorized",code:401});
}else{
req.JWT.isValid = true;
}
}catch( err ){
console.error( err );
return res.status(401).send({error:"Unauthorized",code:401});
}
next();
}else{
return res.status(401).send({error:"Unauthorized",code:401});
}
}
module.exports = {
middleware
};

View File

@@ -0,0 +1,14 @@
"use strict";
/// System implementation dependences
require('dotenv').config();
/// Unit testing dependences
const assert = require("assert");
describe('Example' , () => {
it('finds list', async () => {
assert.equal( 0,0 );
})
});