fix(loads): Normalize dates on post and patch (remove time from dates)

This commit is contained in:
Josepablo C
2025-07-21 12:07:46 -06:00
parent c9d5566288
commit 43bcc7c0f3
72 changed files with 118 additions and 3273 deletions

View File

@@ -1,6 +1,6 @@
"use strict";
const { getModel } = require( '../../../lib/Models' );
const { getPagination, genKey } = require( '../../../lib/Misc.js' );
const { getPagination, genKey, normalizeDate } = require( '../../../lib/Misc.js' );
const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler.js' );
const { onPatchEvent } = require('../../../lib/Handlers/Loads.handler');
const Model = getModel('loads');
@@ -275,6 +275,25 @@ const getById = async(req, res) => {
}
};
function normalizeDatesFromData( data ){
let fields2normalize = [
"contract_start_date",
"contract_end_date",
"est_loading_date",
"est_unloading_date",
"published_date",
"loaded_date",
"transit_date",
"delivered_date",
];
for( const item of fields2normalize ){
if( Object.hasOwn( data, item ) ){
data[ item ] = normalizeDate( data[ item ] )
}
}
return data;
}
function getDataToModify( data ){
/**
* Take the only fields from model that
@@ -329,7 +348,7 @@ function getDataToModify( data ){
throw "nothing to change";
}
return filtered_data;
return normalizeDatesFromData( filtered_data );
}
const patchLoad = async(req, res) => {
try{
@@ -357,23 +376,92 @@ const patchLoad = async(req, res) => {
}
};
function getDataToCreate( data ){
/**
* Take the only fields from model that
* should be modifiable by the client.
* The rest are populated on demand by the event handlers.
*/
let data_fields = {
carrier: null,
vehicle: null,
driver: null,
bidder: null,
alert_list: null,
origin_warehouse: null,
destination_warehouse: null,
origin: null,
origin_geo: null,
destination: null,
destination_geo: null,
categories: null,
product: null,
truck_type: null,
tyre_type: null,
weight: null,
estimated_cost: null,
distance: null,
actual_cost: null,
status: null,
load_status: null,
contract_start_date: null,
contract_end_date: null,
est_loading_date: null,
est_unloading_date: null,
published_date: null,
loaded_date: null,
transit_date: null,
delivered_date: null,
notes: null,
};
let filtered_data = {};
if( Object.keys( data_fields ).length === 0 ){
throw "no data to add";
}
for ( const [key, value] of Object.entries( data_fields ) ) {
if( Object.hasOwn( data, key ) ){
filtered_data[ key ] = data[ key ];
}
}
if( Object.keys( filtered_data ).length === 0 ){
throw "no data to add";
}
return normalizeDatesFromData( filtered_data );
}
const postLoad = async(req, res) => {
try{
const companyId = req.context.companyId;
const userId = req.context.userId;
const user_name = req.context.user.first_name;
let user_name;
if( req.context.user.last_name ){
user_name = `${req.context.user.first_name} ${req.context.user.last_name}`;
}else{
user_name = req.context.user.first_name;
}
const permissions = req.context.permissions;
const data = req.body;
if( !data ){
if( !req.body ){
throw "Load data not sent";
}
if(permissions !== "role_shipper" ){
throw "You can't create loads";
}
const data = getDataToCreate( req.body );
data.company = companyId;
data.posted_by = userId;
data.name = user_name;
const load = new Model( data );/// ToDo, check data content and normalize Dates!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
data.posted_by_name = user_name;
const load = new Model( data );
await load.save();
const id = "" + load._id;

View File

@@ -16,9 +16,9 @@
}
},
"version" : {
"version" : "1.5.1",
"version" : "1.5.2",
"name": "ETA Beta",
"date":"06/2025"
"date":"21/07/2025"
},
"S3" : {
"bucket": "etaviaporte",

View File

@@ -12,11 +12,18 @@ const proposalsModel = getModel('proposals');
*/
async function onDelivered( userId, elementId ){
const load = await loadsModel.findById( elementId );
if( !load ){
/// Nothing to do, invalid load
return;
}
const proposal_list = await proposalsModel.find({
load: elementId,
is_accepted: true,
is_completed: false
});
const vehicle_list = await vehiclesModel.find({
active_load: elementId
});

View File

@@ -39,6 +39,19 @@ function genKey( len = 6, key="" ){
return otp_str.slice(0,len);
}
/**
* Takes a datetime input and produces a date only output.
* @param {*} date
*/
function normalizeDate( date ){
let in_date = new Date( date );
let year = in_date.getFullYear();
let monthIndex = in_date.getMonth();
let day = in_date.getDate();
// new Date(year, monthIndex, day, hours, minutes, seconds, milliseconds);
return new Date( year, monthIndex, day, 0, 0, 0, 0 );
}
function getPagination( query ){
let limit = {
page : 0,
@@ -101,4 +114,4 @@ async function downloadFile( bucket, key, obj_id ){
return s3resp;
}
module.exports = { genKey , toSha256, getPagination, getPage, queryPage, downloadFile};
module.exports = { genKey , toSha256, normalizeDate, getPagination, getPage, queryPage, downloadFile};

View File

@@ -1,17 +0,0 @@
# 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 server /app/server
COPY package.json /app
COPY dotenv /app/.env
RUN apk add bash bash-completion vim
RUN npm install --include=dev
EXPOSE 3000
ENTRYPOINT npm start

View File

@@ -1 +0,0 @@
SERVER_PORT=3000

View File

@@ -1,56 +0,0 @@
{
"name": "etaapi",
"version": "1.0.0",
"description": "ETA API",
"main": "index.js",
"scripts": {
"start": "nodemon server/src/",
"dev": "nodemon server/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.19.2",
"express-fileupload": "^1.4.1",
"express-jwt": "^8.4.1",
"form-data": "^4.0.0",
"graphql": "^16.9.0",
"graphql-http": "^1.22.1",
"graphql-scalars": "^1.23.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

@@ -1,69 +0,0 @@
#! /bin/bash
REGISTRY_USER="jcruzbaas@gmail.com"
REGISTRY_NAME="registry.gitlab.com/jcruzbaasworkspace/enruta"
REGISTRY_SERVER="registry.gitlab.com"
CONTAINER_VERSION="2"
# Requirements
# Docker: Node v18-alpine
function build_docker(){
#Global ENV VAR: REGISTRY_NAME
#Global ENV VAR: CONTAINER_NAME
if [[ $# -lt 1 ]]; then
echo $0 "[conatiner name]"
return -1
fi
CONTAINER_NAME=$1
set -x
docker rmi -f "$REGISTRY_NAME/$CONTAINER_NAME"
docker buildx build --no-cache -t $REGISTRY_NAME/$CONTAINER_NAME ./
set +x
}
function upload_image(){
#Global ENV VAR: REGISTRY_NAME
#Global ENV VAR: GITLAB_CICD_REGISTRY_TOKEN
#Global ENV VAR: CONTAINER_NAME
docker login $REGISTRY_SERVER -u "$REGISTRY_USER" -p "$GITLAB_CICD_REGISTRY_TOKEN"
set -x
docker push "$REGISTRY_NAME/$CONTAINER_NAME":$CONTAINER_VERSION
set +x
}
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:$CONTAINER_VERSION
}
function deploy_local(){
# docker login --username $AWS_ECR_USER --password $DOCKER_PWD $REGISTRY_NAME
deploy_uservice $CONTAINER_NAME $PUBLIC_PORT $PRIVATE_PORT $REGISTRY_NAME
}
function deploy(){
# Global ENV VAR: GITLAB_CICD_REGISTRY_TOKEN
# Global Env Var: REGISTRY_NAME
# Global Env Var: REGISTRY_USER
# Global Env Var: CONTAINER_NAME
# Global Env Var: PUBLIC_PORT
# Global Env Var: PRIVATE_PORT
# Global Env Var: SYSTEM_HOSTNAME
set -x
ssh -i ~/.ssh/gitlab_runner gitlab-runner@$SYSTEM_HOSTNAME "docker login $REGISTRY_SERVER --username $REGISTRY_USER --password $GITLAB_CICD_REGISTRY_TOKEN $REGISTRY_NAME && ./deploy_uservice.sh $CONTAINER_NAME $PUBLIC_PORT $PRIVATE_PORT $REGISTRY_NAME && exit"
set +x
}

View File

@@ -1,5 +0,0 @@
# REST API Framework
## Architecture Design
![alt text](docs/assets/APIDesign.png)

View File

@@ -1,55 +0,0 @@
{
"authentication": {
"pwdSecret":"Nx2g_IWo2Zt_LS$+",
"jwtSecret":"9o3BBz0EsrwXXiwEJ/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" : "2.0.0",
"name": "ETA Beta",
"date":"08/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",
"sql":{
"host":"srv765.hstgr.io",
"port":3306,
"user":"u947463964_sysadmin",
"password":"3^K/47^h5pP",
"database":"u947463964_etaviaporte"
}
}

View File

@@ -1,118 +0,0 @@
<mxfile host="app.diagrams.net" modified="2024-04-12T18:18:19.489Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0" etag="IcOi7BUkbZV7L-Fstefg" version="24.2.3" type="device">
<diagram name="Page-1" id="TZpYyL79LOodfNr5sDYv">
<mxGraphModel dx="1656" dy="1032" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="pRzEEGsYkG0vH22XolYE-5" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="80" y="540" width="720" height="130" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-1" value="&lt;div&gt;Connections&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="180" y="590" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-2" value="Controller (ExpressJS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="540" y="590" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-3" value="EventManager" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="360" y="590" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-6" value="&lt;div&gt;SystemServices&lt;/div&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="97.5" y="560" width="100" height="30" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-8" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="80" y="400" width="400" height="130" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-10" value="Resources" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="280" y="450" width="180" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-9" value="Shared" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="97.5" y="420" width="87.5" height="30" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-11" value="Models" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="97.5" y="450" width="172.5" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-12" value="Controller (Router)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="490" y="400" width="310" height="130" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-14" value="entry_point with safe exit events" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="80" y="680" width="720" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-17" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-2" target="pRzEEGsYkG0vH22XolYE-12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="510" y="550" as="sourcePoint" />
<mxPoint x="560" y="500" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-18" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="80" y="90" width="720" height="300" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-19" value="Controller" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="662.5" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-20" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="232.5" y="270" width="420" height="100" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-21" value="Domain" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="97.5" y="150" width="690" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-22" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-12" target="pRzEEGsYkG0vH22XolYE-19">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="510" y="540" as="sourcePoint" />
<mxPoint x="560" y="490" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-23" value="Application" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="102.5" y="110" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-24" value="Repository" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="102.5" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-25" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-19" target="pRzEEGsYkG0vH22XolYE-21">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="542.5" y="530" as="sourcePoint" />
<mxPoint x="592.5" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-21" target="pRzEEGsYkG0vH22XolYE-20">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="542.5" y="530" as="sourcePoint" />
<mxPoint x="592.5" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-28" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-21" target="pRzEEGsYkG0vH22XolYE-24">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="542.5" y="530" as="sourcePoint" />
<mxPoint x="592.5" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-29" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-24" target="pRzEEGsYkG0vH22XolYE-11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="510" y="530" as="sourcePoint" />
<mxPoint x="90" y="340" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-30" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-3" target="pRzEEGsYkG0vH22XolYE-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="510" y="520" as="sourcePoint" />
<mxPoint x="560" y="470" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-32" value="Public" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="252.5" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-33" value="Interfaces" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="382.5" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-34" value="Events" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#333333;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="512.5" y="290" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="pRzEEGsYkG0vH22XolYE-35" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="pRzEEGsYkG0vH22XolYE-34" target="pRzEEGsYkG0vH22XolYE-10">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="450" y="480" as="sourcePoint" />
<mxPoint x="500" y="430" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -1,169 +0,0 @@
'use strict';
const Application = require('../Domain');
function dummy_middleware( req, res, next ){
return res.status(500).send({ error:"Not implemented yet" });
}
/// Block access to the next list of endpoints if JWT is not valid
async function access_control_with_jwt( req, res, next){
if( ! req.JWT?.isValid ){
return res.status(401).send({error:"Unauthorized",code:401});
}
return next();
}
async function authorize(req, res, next) {
try{
const email = req.body.email;
const password = req.body.password;
const data = await Application.authorize_credentials( email , password );
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function refresh_session(req, res, next) {
try{
const session_token = req.params.session_token;
const data = await Application.authorize_token( session_token );
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function check_account(req, res, next){
try{
const email = req.params.email;
const data = await Application.check_account( email );
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function post_signup(req, res, next) {
try{
const email = req.body.email;
const password = req.body.password;
const data = await Application.genOTPChecksum( email, password, "signup" );
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function patch_signup(req, res, next) {
try{
const email = req.body.email;
const password = req.body.password;
const otp = req.body.otp;
const checksum = req.body.checksum;
const data = await Application.signup( email , password, otp, checksum);
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function post_recover(req, res, next){
try{
const email = req.body.email;
const password = req.body.password;
const data = await Application.genOTPChecksum( email, password, "recover" );
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
async function patch_recover(req, res, next){
try{
const email = req.body.email;
const password = req.body.password;
const otp = req.body.otp;
const checksum = req.body.checksum;
const data = await Application.recover( email , password, otp, checksum);
if( data.error ){
const error = data.error;
return res.status( error.code ).send( { error : error.msg } );
}
return res.send( data );
}catch(error){
console.error( error );
return res.status( 500 ).send( { error } );
}
}
function AppInit(){
Application.init();
}
module.exports = {
AppInit,
hooks : {
before: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [],
del : [],
get : [],
patch : [],
post : [],
put : []
},
after: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [
{ '/register': dummy_middleware }
],
del : [],
get : [
{ '/check-account/:email' : check_account },
{ '/authorize/:session_token': refresh_session }
],
patch : [
{ '/signup' : patch_signup },
{ '/recover' : patch_recover }
],
post : [
{ '/authorize' : authorize },
{ '/signup' : post_signup },
{ '/recover' : post_recover }
],
put : []
},
}
};

View File

@@ -1,13 +0,0 @@
'use strict';
const { hooks, AppInit } = require( './hooks' );
const GenericController = require('../../Lib');
class Account extends GenericController {
init(){
super.init();
AppInit();
}
}
module.exports = new Account( hooks );

View File

@@ -1,217 +0,0 @@
'use strict';
const Repository = require('../Repository');
const jsonwebtoken = require('jsonwebtoken');
const { ModuleName,
toSha256,
publishEvent,
registerEvent,
jwtRenewalTimeout,
jwtTimeout,
jwtOptions,
jwtSecret,
tokenSecret,
pwdSecret,
genErrorResponse } = require('../Interfaces');
class Account {
constructor(){
this.Events = {
/** Event_Id : callback */
};
}
init(){
/// Setup application events
const Events = this.Events;
for ( const [event, callback] of Object.entries( Events ) ) {
const event_id = ModuleName + event
Interfaces.registerEvent( event_id , callback );
}
}
genOTP( email ){
const len = 5;
const shacode = toSha256( email + new Date() + tokenSecret );
const otp_hex = shacode.slice(0 , len );
const otp_dec = Number.parseInt( otp_hex , 16 );
return ""+otp_dec;
}
genSafePassword( password ){
return toSha256( password + pwdSecret );
}
async check_account( email ){
const projection = ["id","email","password","company_id"];
const user = await Repository.getByEmail( email , projection );
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 genOTPChecksum( email, password, reason="signup" ){
let event_id = reason;
if( reason === "signup" ){
event_id = "getchecksum:signup";
const it_exists = await Repository.getByEmail( email );
if( it_exists ){
return genErrorResponse( "User already registered!" );
}
}else if( reason === "recover" ){
event_id = "getchecksum:recover";
}else{
return genErrorResponse( "OPT Event type not defined" , 500 );
}
const otp = this.genOTP( email );
const content = { OTP : otp, user_name : email, email };
const checksum_entry = {
email,
password,
otp
};
const checksum = toSha256( JSON.stringify(checksum_entry)).slice(0, 32);
publishEvent( event_id , content );
return { checksum };
}
async signup( email, password, otp, checksum ){
const it_exists = await Repository.getByEmail( email );
if( it_exists ){
return genErrorResponse( "User already registered!" );
}
const checksum_entry = {email, password, otp};
const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).slice(0, 32);
if( recomputed_checksum != checksum ){
return genErrorResponse( "Wrong OTP" );
}
await Repository.createOne( email, this.genSafePassword( password ) );
const content = { user_name : email, email };
publishEvent( "signupconfirmed" , content );
return await this.authorize_credentials( email, password );
}
async recover( email, password, otp, checksum ){
const user = await Repository.getByEmail( email );
if( !user ){
return genErrorResponse( "Email is not registered!" );
}
const checksum_entry = {email, password, otp};
const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).slice(0, 32);
if( recomputed_checksum != checksum ){
return genErrorResponse( "Wrong OTP" );
}
await Repository.updatePassword( user.id, this.genSafePassword( password ) );
return await this.authorize_credentials( email, password );
}
async authorize_credentials( email, password ){
const user = await Repository.findByEmailPassword( email, this.genSafePassword( password ) );
if( !user ){
return genErrorResponse( "Not able to log in", 401 );
}
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 );
await Repository.addSessionToken( user.id, session_token, session_token_exp );
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
};
}
async authorize_token( token ){
const user = await Repository.findBySessionToken( token );
if( !user ){
return genErrorResponse( "Invalid Session Token", 401 );
}
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 );
await Repository.addSessionToken( user.id, session_token, session_token_exp );
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
};
}
};
module.exports = new Account();

View File

@@ -1,47 +0,0 @@
'use strict';
const ModuleName = "App:Account:";
const { toSha256 } = require('../../../Shared/ShaUtils');
const { authentication } = require('../../../../config/apiConfig.json');
const { genErrorResponse } = require('../../../Shared/ErrorResponse');
const SharedResources = require('../../../Shared/Resources');
function registerEvent( event_id , callback ){
console.log(`Loading event ${event_id}`);
const EventBus = SharedResources.get("SysS:EventManager");
EventBus.addEvent( event_id , callback );
}
function publishEvent( event , data = null ){
const EventBus = SharedResources.get("SysS:EventManager");
const AppEventDomain = ModuleName;
const event_id = AppEventDomain + event;
console.log( event_id );
EventBus.publishEvent( event_id , data );
}
const tokenSecret = authentication.tokenSecret;
const jwtRenewalTimeout = authentication.jwtRenewalTimeout;
const jwtTimeout = authentication.jwtTimeout;
const jwtOptions = authentication.jwtOptions;
const jwtSecret = authentication.jwtSecret;
const pwdSecret = authentication.pwdSecret;
module.exports = {
toSha256,
tokenSecret,
jwtRenewalTimeout,
jwtTimeout,
jwtOptions,
jwtSecret,
pwdSecret,
genErrorResponse,
registerEvent,
publishEvent
};

View File

@@ -1,97 +0,0 @@
'use strict';
const { getModel } = require('../../../../Shared/Models/Objection');
const Users = getModel('users');
const Sessions = getModel('users_sessions');
const Companies = getModel('companies');
class SpecificModelRepository{
constructor(){}
async populate( user ){
if( user.company_id ){
const company = await Companies.query().findById( user.company_id );
user.company = company;
}else{
user.company = null;
}
return user;
}
async getByEmail( email ){
const user = await Users.query()
.select( "*" )
.where("email","=",email).first();
return user;
}
async createOne( email, safe_password ){
const user = await Users.query().insert({
email,
password : safe_password,
"name":"No name",
"last_name":"No lastname",
"createdAt" : new Date().toISOString()
});
return await this.populate( user );
}
async findByEmailPassword( email, safe_password ){
const user = await Users.query().select('*')
.where("email","=",email)
.where("password","=",safe_password)
.first();
if( user ){
return await this.populate( user );
}else{
return null;
}
}
async updateSessionToken( old_token, token, expiration ){
const entry = await Sessions.query().select('*').where('token','=',old_token).first();
const data = {
token,
expiration,
};
if( entry ){
return await Sessions.query().patch( data ).where('token','=',old_token).first();
}
return null;
}
async updatePassword( userId , safe_password ){
return await Users.query()
.findById( userId )
.patch( { password : safe_password } );
}
async addSessionToken( userId , token, expiration ){
const entry = await Sessions.query().select('*').where("user_id",'=',userId).first();
const data = {
token,
expiration : expiration.toISOString(),
};
if( entry ){
return await Sessions.query()
.findById( entry.id )
.patch(data);
}else{
data.user_id = userId;
return await Sessions.query().insert( data );
}
}
async findBySessionToken( token ){
const session = await Sessions.query().select("*").where("token","=",token).first();
const user = await Users.query().findById( session.user_id );
if( user ){
return await this.populate( user );
}else{
return null;
}
}
}
module.exports = new SpecificModelRepository();

View File

@@ -1,5 +0,0 @@
'use strict';
const SpecificModelRepository = require('./Objection');
module.exports = SpecificModelRepository;

View File

@@ -1,38 +0,0 @@
'use strict';
const Application = require('../Domain');
function dummy_middleware( req, res, next ){
Application.trigger_example_event( {
example : "This is an example of event data"
} )
return res.status(500).send({ error:"Not implemented yet" });
}
function AppInit(){
Application.init();
}
module.exports = {
AppInit,
hooks : {
before: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [],
del : [],
get : [],
patch : [],
post : [],
put : []
},
after: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [ { "/test" : dummy_middleware } ],
del : [],
get : [],
patch : [],
post : [],
put : []
},
}
};

View File

@@ -1,13 +0,0 @@
'use strict';
const { hooks, AppInit } = require( './hooks' );
const GenericController = require('../../Lib');
class Company extends GenericController {
init(){
super.init();
AppInit();
}
}
module.exports = new Company( hooks );

View File

@@ -1,34 +0,0 @@
'use strict';
const Repository = require('../Repository');
const Interfaces = require('../Interfaces');
class Company {
constructor(){
this.Events = {
/** Event_Id : callback */
"Example" : this.event_handler_example
};
}
init(){
/// Setup application events
const Events = this.Events;
/// Setup application events
for ( const [event, callback] of Object.entries( Events ) ) {
const event_id = Interfaces.ModuleName + event
Interfaces.registerEvent( event_id , callback );
}
}
trigger_example_event( data ){
Interfaces.publishEvent( "Example", data );
}
event_handler_example( data ){
console.log( "CompanyDomain event" );
console.log( data );
}
};
module.exports = new Company();

View File

@@ -1,27 +0,0 @@
'use strict';
const ModuleName = "App:Company:";
const SharedResources = require('../../../Shared/Resources');
function registerEvent( event_id , callback ){
const EventBus = SharedResources.get("SysS:EventManager");
console.log(`Loading event ${event_id}`);
EventBus.addEvent( event_id , callback );
}
function publishEvent( event , data = null ){
const EventBus = SharedResources.get("SysS:EventManager");
const AppEventDomain = ModuleName;
const event_id = AppEventDomain + event;
console.log( event_id );
EventBus.publishEvent( event_id , data );
}
module.exports = {
ModuleName,
registerEvent,
publishEvent
};

View File

@@ -1,3 +0,0 @@
'use strict';
module.exports = {};

View File

@@ -1,5 +0,0 @@
'use strict';
const SpecificModelRepository = require('./Objection');
module.exports = SpecificModelRepository;

View File

@@ -1,116 +0,0 @@
'use strict';
const express = require('express');
class GenericController{
constructor( hooks ){
this.router = express.Router();
this.hooks = hooks;
}
init(){
const { before, after } = this.hooks;
if( before ){
this.populate_hooks( before );
}
if( after ){
this.populate_hooks( after );
}
return this;
}
populate_hooks( hooks ){
if( (hooks.all) && (hooks.all.length > 0) ){
this.populate_hooks_by_verb( 'all' , hooks.all );
}
if( (hooks.delete) && (hooks.delete.length > 0) ){
this.populate_hooks_by_verb( 'del', hooks.del );
}
if( (hooks.get) && (hooks.get.length > 0) ){
this.populate_hooks_by_verb( 'get', hooks.get );
}
if( (hooks.patch) && (hooks.patch.length > 0) ){
this.populate_hooks_by_verb( 'patch', hooks.patch );
}
if( (hooks.post) && (hooks.post.length > 0) ){
this.populate_hooks_by_verb( 'post', hooks.post );
}
if( (hooks.put) && (hooks.put.length > 0) ){
this.populate_hooks_by_verb( 'put', hooks.put );
}
}
add_hook_with_endpoint( http_verb, endpoint, middleware ){
switch( http_verb ){
case 'all':
this.router.use( endpoint , middleware );
break;
case 'del':
this.router.delete(endpoint, middleware);
break;
case 'get':
this.router.get(endpoint, middleware);
break;
case 'patch':
this.router.patch(endpoint, middleware);
break;
case 'post':
this.router.post(endpoint, middleware);
break;
case 'put':
this.router.put(endpoint, middleware);
break;
default:
return;
}
}
add_simple_hook( http_verb, middleware ){
switch( http_verb ){
case 'all':
this.router.use( middleware );
break;
case 'del':
this.router.delete( middleware );
break;
case 'get':
this.router.get( middleware );
break;
case 'patch':
this.router.patch( middleware );
break;
case 'post':
this.router.post( middleware );
break;
case 'put':
this.router.put( middleware );
break;
default:
return;
}
}
populate_hooks_by_verb( http_verb, hook_list ){
for (let custom_hook of hook_list ){
if( typeof custom_hook === "object" ){
for (let entry of Object.entries(custom_hook) ){
const endpoint = entry[0];
const callback = entry[1];
/// Add this hook as an (endpoint, middleware function) pair
this.add_hook_with_endpoint( http_verb, endpoint, callback );
}
}else{
/// Add this hook as a middleware function
this.add_simple_hook( http_verb, custom_hook );
}
}
}
}
module.exports = GenericController;

View File

@@ -1,53 +0,0 @@
'use strict';
const { DateResolver, DateTimeResolver } = require('graphql-scalars');
const { Account, User, Company, getUserById, getCompanyById, findUsersPage, findCompaniesPage } = require('../../Domain');
//////////////////////////////////////////////
// Queries
//////////////////////////////////////////////
async function account( args, context ) {
const account = new Account( context.requestContext.userId );
return account;
}
async function profile( args, context ) {
const profile = new User( context.requestContext.userId );
return profile;
}
async function company( args, context ) {
const company = new Company( context.requestContext.companyId );
return company;
}
async function companyById( args, context ) {
return getCompanyById( args.id );
}
async function userById( args, context ) {
return getUserById( args.id );
}
async function findCompanies( args, context ) {
const { filters, elements, page } = args;
return findCompaniesPage( filters, elements, page );
}
async function findUsers( args, context ) {
const { filters, elements, page } = args;
return findUsersPage( filters, elements, page );
}
/////////////////////////////////////////////////
// Mutations
/////////////////////////////////////////////////
module.exports = {
account,
profile,
company,
companyById,
userById,
findCompanies,
findUsers
};

View File

@@ -1,146 +0,0 @@
type Query {
account : Account!
profile : User!
company : Company!
companyById( id : Int! ) : PublicCompany
userById( id : Int! ) : PublicUser
findCompanies( filters : FindCompanyFilterInput!, elements: Int!, page: Int! ) : PublicCompanyFound!
findUsers( filters : FindUserFilterInput!, elements: Int!, page: Int! ) : PublicUserFound!
}
scalar DateTime
type Session {
token : String!
expiration : DateTime!
}
type Account {
user : User!
sessions( limit: Int , offset : Int ) : [Session]!
}
type LocationCategory{
id : Int!
category : String!
}
type Location{
id : Int!
company : Company!
type : String!
state : String!
city : String!
country : String!
zipcode : String!
address_line1 : String!
address_line2 : String
categories : [LocationCategory]!
}
type TruckType{
id : Int!
category : String!
}
type CompanyCategory{
id : Int!
category : String!
}
type Company {
id : Int!
owner : User!
staff : [User]!
type : String!
is_hidden : Boolean!
is_active : Boolean!
name : String!
description : String
createdAt : DateTime!
locations : [Location]!
categories : [CompanyCategory]!
truck_types : [TruckType]!
}
type User {
id : Int!
company : Company
phone : String
email : String!
name : String!
last_name : String!
job_role : String!
permissions : String!
createdAt : DateTime!
is_active : Boolean!
locations( limit: Int , offset : Int ) : [Location]!
}
type PublicCompany {
id : Int!
owner : PublicUser!
staff : [PublicUser]!
type : String!
name : String!
description : String
createdAt : DateTime!
locations : [Location]!
categories : [CompanyCategory]!
truck_types : [TruckType]!
}
type PublicCompanyFound {
count : Int!
list : [PublicCompany]!
}
input FindCompanyFilterInput {
name : String
type : String
state : String
city : String
truck_type : String
categories : String
}
type PublicUser {
id : Int!
company : Company
phone : String
email : String!
name : String!
last_name : String!
job_role : String!
createdAt : DateTime!
locations( limit: Int , offset : Int ) : [Location]!
}
type PublicUserFound {
count : Int!
list : [PublicUser]!
}
input FindUserFilterInput {
email : String
name : String
last_name : String
employee_id : String
company_name : String
job_role : String
}

View File

@@ -1,8 +0,0 @@
'use strict';
const fs = require('fs');
const { join } = require('path');
const { buildSchema } = require('graphql');
const schema = fs.readFileSync(join(__dirname, './schema.graphql'), 'utf8');
module.exports = buildSchema( schema );

View File

@@ -1,50 +0,0 @@
'use strict';
const { createHandler } = require("graphql-http/lib/use/express");
/// Include graphql schema and resolvers
const schemaDescription = require('./graphql/schema.js');
const schemaResolvers = require('./graphql/resolvers.js');
const { initEvents } = require('../Domain');
async function test(req, res, next){
console.log( req.requestContext );
res.status(200).send({
msg : "It is alive!"
});
}
module.exports = {
AppInit : initEvents, /// Dummy App Init
hooks : {
before: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [],
del : [],
get : [ test ],
patch : [],
post : [],
put : []
},
after: {
/**Array of middleware functions or objects lile { endpoint, middleware }*/
all : [],
del : [],
get : [],
patch : [],
post : [
{ '/graphql' :
createHandler({
schema: schemaDescription,
rootValue : schemaResolvers,
context: async (req, params) => { return { requestContext : req.raw.requestContext }; },
graphiql: true
})
}
],
put : []
}
}
};

View File

@@ -1,13 +0,0 @@
'use strict';
const { hooks, AppInit } = require( './hooks' );
const GenericController = require('../../Lib');
class PrivateResources extends GenericController {
init(){
super.init();
AppInit();
}
}
module.exports = new PrivateResources( hooks );

View File

@@ -1,389 +0,0 @@
'use strict';
const Repository = require('../Repository');
const Interfaces = require('../Interfaces');
class TruckType {
constructor( category ){
this._category = category;
}
async id(){
return this._category.id;
}
async category(){
return this._category.category;
}
}
class Category {
constructor( category ){
this._category = category;
}
async id(){
return this._category.id;
}
async category(){
return this._category.category;
}
}
class Location {
constructor( location ){
this._location = location;
this._categories = null;
}
async populate_content(){
if( !this._categories ){
this._categories = await Repository.getCategories( 'location', this._location.id );
this._categories.map( (element) => {
return new Category( element );
});
}
}
async company_id(){
await this.populate_content();
this._location.company_id;
}
async type(){
await this.populate_content();
this._location.type;
}
async state(){
await this.populate_content();
this._location.state;
}
async city(){
await this.populate_content();
this._location.city;
}
async country(){
await this.populate_content();
this._location.country;
}
async zipcode(){
await this.populate_content();
this._location.zipcode;
}
async address_line1(){
await this.populate_content();
this._location.address_line1;
}
async address_line2(){
await this.populate_content();
this._location.address_line2;
}
async categories(){
await this.populate_content();
return this._categories;
}
}
class Company {
constructor( companyId , owner = null ){
this._companyId = companyId;
this._company = null;
this._owner = owner;
this._staff = null;
this._locations = null;
this._truck_types = null;
this._categories = null;
}
setCompany( company ){
this._company = company;
}
async populate_content(){
if(! this._company ){
this._company = await Repository.getCompanyById( this._companyId );
}
if(! this._owner ){
this._owner = new User( this._company.owner_id, this );
await this._owner.populate_content();
}
if( !this._locations ){
this._locations = await Repository.getLocations( 'company', this._companyId );
this._locations.map( (element) => {
return new Location( element );
});
}
if( !this._truck_types ){
this._truck_types = await Repository.getTruckTypes( 'company', this._companyId );
this._truck_types.map( (element) => {
return new TruckType( element );
});
}
if( !this._categories ){
this._categories = await Repository.getCategories( 'company', this._companyId );
this._categories.map( (element) => {
return new Category( element );
});
}
if( !this._staff ){
/// ToDo: Populate _staff
this._staff = []
}
}
async id(){
return this._companyId;
}
async owner(){
await this.populate_content();
return this._owner;
}
async staff(){
await this.populate_content();
return this._staff;
}
async type(){
await this.populate_content();
return this._company.type;
}
async is_hidden(){
await this.populate_content();
return this._company.is_hidden;
}
async is_active(){
await this.populate_content();
return this._company.is_active;
}
async name(){
await this.populate_content();
return this._company.name;
}
async description(){
await this.populate_content();
return this._company.description;
}
async createdAt(){
await this.populate_content();
return this._company.createdAt;
}
async locations(){
await this.populate_content();
return this._locations;
}
async categories(){
await this.populate_content();
return this._categories;
}
async truck_types(){
await this.populate_content();
return this._truck_types;
}
}
class User {
constructor( userId, company = null ){
this._userId = userId;
this._user = null;
this._locations = null;
this._company = company;
}
setUser( user ){
this._user = user;
}
async populate_content(){
if(! this._user ){
this._user = await Repository.getUserById( this._userId );
}
if( (!this._company) && (this._user.company_id) ){
/// Populate only if company is linked to user
this._company = new Company( this._user.company_id, this );
await this._company.populate_content();
}
if( !this._locations ){
this._locations = await Repository.getLocations( 'user', this._userId );
this._locations.map( (element) => {
return new Location( element );
});
}
}
async id(){
return this._userId;
}
async company(){
await this.populate_content();
return this._company;
}
async phone(){
await this.populate_content();
return this._user.phone;
}
async email(){
await this.populate_content();
return this._user.email;
}
async name(){
await this.populate_content();
return this._user.name;
}
async last_name(){
await this.populate_content();
return this._user.last_name;
}
async job_role(){
await this.populate_content();
return this._user.job_role;
}
async permissions(){
await this.populate_content();
return this._user.permissions;
}
async createdAt(){
await this.populate_content();
return this._user.createdAt;
}
async is_active(){
await this.populate_content();
return this._user.is_active;
}
async locations(){
await this.populate_content();
return this._locations;
}
}
class Account {
constructor( userId ){
this._userId = userId;
this._sessions = null;
this._user = null;
}
setUser( user ){
this._user = user;
}
async user(){
if( this._user ){
return this._user;
}
this._user = new User( this._userId );
return this._user;
}
async populate_content(){
if( this._sessions ){
return;
}
this._sessions = await Repository.getSessions( this._userId );
this._sessions.map( (item) => {
return {
token : item.token,
expiration : item.expiration
};
} );
}
async sessions(){
await this.populate_content();
return this._sessions;
}
};
async function getUserById( id ) {
const user = await Repository.getUserById( id );
if( user ){
const user_obj = new User( user.id );
user_obj.setUser(user);
return user_obj;
}else{
return null;
}
}
async function getCompanyById( id ) {
const company = await Repository.getCompanyById( id );
if( company ){
const company_obj = new Company( user.id );
company_obj.setCompany(company);
return company_obj;
}else{
return null;
}
}
async function findUsersPage( filters, elements, page ) {
/// ToDo: Populate find
const list = [];
list.map( (element) => {
const user = new User( element.id );
user.setUser( element );
return user;
} );
return {
count : list.length,
list
};
}
async function findCompaniesPage( filters, elements, page ) {
/// ToDo: Populate find
const list = [];
list.map( (element) => {
const company = new Company( element.id );
company.setCompany( element );
return company;
} );
return {
count : list.length,
list
};
}
/**
* List of Events to handle in this App Domain
*/
const Events = {
/** Event_Id : callback */
};
function initEvents(){
console.log("Init GraphQL Events");
/// Setup application events
for ( const [event, callback] of Object.entries( Events ) ) {
const event_id = Interfaces.ModuleName + event
Interfaces.registerEvent( event_id , callback );
}
}
module.exports = {
initEvents,
Account,
User,
Company,
Location,
Category,
TruckType,
getUserById,
getCompanyById,
findUsersPage,
findCompaniesPage,
};

View File

@@ -1,27 +0,0 @@
'use strict';
const ModuleName = "App:PrivateResources:";
const SharedResources = require('../../../Shared/Resources');
function registerEvent( event_id , callback ){
const EventBus = SharedResources.get("SysS:EventManager");
console.log(`Loading event ${event_id}`);
EventBus.addEvent( event_id , callback );
}
function publishEvent( event , data = null ){
const EventBus = SharedResources.get("SysS:EventManager");
const AppEventDomain = ModuleName;
const event_id = AppEventDomain + event;
console.log( event_id );
EventBus.publishEvent( event_id , data );
}
module.exports = {
ModuleName,
registerEvent,
publishEvent
};

View File

@@ -1,68 +0,0 @@
'use strict';
const { getModel } = require('../../../../Shared/Models/Objection');
const Users = getModel('users');
const Sessions = getModel('users_sessions');
const Companies = getModel('companies');
const Locations = getModel('locations');
const userLocations = getModel('user_locations');
const companyTruckTypes = getModel('company_truck_types');
const companyCategories = getModel('company_categories');
const locationTruckTypes = getModel('location_truck_types');
const locationCategories = getModel('location_categories');
class SpecificModelRepository{
constructor(){}
async getUserById( userId ){
return await Users.query().findById( userId );
}
async getCompanyById( companyId ){
return await Companies.query().findById( companyId );
}
async getSessions( userId ){
return await Sessions.query().where("user_id","=",userId);
}
async getLocations( location_type, elementId ){
switch( location_type ){
case 'company':
return await Locations.query().where("company_id","=",elementId);
case 'user':
///ToDo: Implement join on Locations and userLocations
return [];
default:
return [];
}
}
async getCategories( category_type, elementId ){
switch( category_type ){
case 'company':
return await companyCategories.query().where("company_id","=",elementId);
case 'location':
///ToDo: Implement join on companyCategories and locationCategories
return [];
default:
return [];
}
}
async getTruckTypes( search_type, elementId ){
switch( search_type ){
case 'company':
return await companyTruckTypes.query().where("company_id","=",elementId);
case 'location':
///ToDo: Implement join on companyTruckTypes and locationTruckTypes
return [];
default:
return [];
}
}
}
module.exports = new SpecificModelRepository();

View File

@@ -1,5 +0,0 @@
'use strict';
const SpecificModelRepository = require('./Objection');
module.exports = SpecificModelRepository;

View File

@@ -1,45 +0,0 @@
'use strict';
/**
* ExpressJS Controller
*/
const express = require('express');
const SystemService = require('../SysS/Lib');
const middlewares = require('./middlewares');
const Resources = require('./resources');
class AppLayerController extends SystemService {
constructor(){
super();
this.app = express();
this.resources = Resources;
}
async setup(){
super.setup();
}
async init(){
super.init();
const app = this.app;
/// Populate context from JWT payload
app.use( middlewares.jwtValidator );
app.use( middlewares.contextGenerator );
for ( const resource of this.resources ){
for ( const [endpoint, controller] of Object.entries( resource ) ) {
controller.init();
app.use( endpoint, controller.router );
}
}
}
app_controller(){
return this.app;
}
}
module.exports = new AppLayerController();

View File

@@ -1,60 +0,0 @@
'use strict';
const apiConfig = require( '../../config/apiConfig.json' );
const jwt = require('jsonwebtoken');
const jwtSecret = apiConfig.authentication.jwtSecret;
const { getModel } = require('../Shared/Models/Objection');
const Users = getModel('users');
function jwtValidator(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});
}
return next();
}else{
/// If no JWT available, ignore and continue
return next();
// return res.status(401).send({error:"Unauthorized",code:401});
}
}
async function contextGenerator( req, res, next ){
if( ! req.JWT?.isValid ){
/// If no JWT available, ignore and continue
return next();
// return res.status(401).send({error:"Unauthorized",code:401});
}
/// Process only if JWT is valid
const userId = req.JWT.payload.sub;
const user = await Users.query().findById( userId ).select( "id", "company_id", "phone", "email", "name", "last_name", "job_role", "permissions", "createdAt", "is_active" );
const companyId = user.company_id;
const job_role = user.job_role;
const permissions = user.permissions;
req.requestContext = {
userId,
companyId,
job_role,
permissions,
user
}
return next();
}
module.exports = {
jwtValidator,
contextGenerator
};

View File

@@ -1,12 +0,0 @@
'use strict';
const Account = require('../Apps/Account/Controller');
const Company = require('../Apps/Company/Controller');
const PrivateResources = require('../Apps/PrivateResources/Controller');
module.exports = [
{ "/account": Account },
{ "/company": Company },
{ "/private": PrivateResources },
];

View File

@@ -1,12 +0,0 @@
'use strict';
function genErrorResponse( msg , code = 400 ){
return {
error : {
code,
msg
}
}
}
module.exports = { genErrorResponse };

View File

@@ -1,24 +0,0 @@
'use strict';
const { Model } = require('objection');
class Companies extends Model {
static get tableName() { return 'companies'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['owner_id','type','is_hidden','is_active','name','description','createdAt'],
properties : {
owner_id : { type : 'integer', minimum : 0 },
type : { type : 'string' , enum : ['carrier', 'shipper'] },
is_hidden : { type : 'boolean', default : true },
is_active : { type : 'boolean', default : true },
name : { type : 'string', maxLength : 100 },
description : { type : 'string', maxLength : 256 },
createdAt : { type : 'date-time' }
}
};
}
}
module.exports = Companies;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class CompanyCategories extends Model {
static get tableName() { return 'company_categories'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['company_id','category'],
properties : {
company_id : { type : 'integer', minimum : 0 },
category : { type : 'string', maxLength : 100 },
}
};
}
}
module.exports = CompanyCategories;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class CompanyTruckTypes extends Model {
static get tableName() { return 'company_truck_types'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['company_id','truck_type'],
properties : {
company_id : { type : 'integer', minimum : 0 },
truck_type : { type : 'string', maxLength : 100 },
}
};
}
}
module.exports = CompanyTruckTypes;

View File

@@ -1,55 +0,0 @@
"use strict";
const users = require('./users.model');
const user_sessions = require('./user_sessions.model');
const companies = require('./companies.model');
const locations = require('./locations.model');
const user_locations = require('./user_locations.model');
const company_truck_types = require('./company_truck_types.model');
const company_categories = require('./company_categories.model');
const location_categories = require('./location_categories.model');
const location_truck_types = require('./location_truck_types.model');
const metadata_categories = require('./metadata_categories.model');
const metadata_cities = require('./metadata_cities.model');
const metadata_products = require('./metadata_products.model');
const metadata_truck_types = require('./metadata_truck_types.model');
function getModel( name ){
switch( name ){
case 'users':
return users;
case 'users_sessions':
return user_sessions;
case 'companies':
return companies;
case 'locations':
return locations;
case 'user_locations':
return user_locations;
case 'company_truck_types':
return company_truck_types;
case 'company_categories':
return company_categories;
case 'location_categories':
return location_categories;
case 'location_truck_types':
return location_truck_types;
case 'metadata_categories':
return metadata_categories;
case 'metadata_cities':
return metadata_cities;
case 'metadata_products':
return metadata_products;
case 'metadata_truck_types':
return metadata_truck_types;
default:
return null;
}
}
module.exports = {
getModel
};

View File

@@ -1,32 +0,0 @@
'use strict';
const { Model } = require('objection');
class LoadAttachments extends Model {
static get tableName() { return 'load_attachments'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'status',
'type',
'createdAt',
'updatedAt',
'doneAt'
],
properties : {
load_id : { type : 'integer' , minimum : 0 },
shipper_id : { type : 'integer' , minimum : 0 },
carrier_id : { type : 'integer' , minimum : 0 },
author_id : { type : 'integer' , minimum : 0 },
status : { type : 'string' , default : 'Draft', enum: ['Draft', 'Done'] },
type : { type : 'string' , enum: ['Draft', 'Done'] },
createdAt : { type : 'date-time' },
updatedAt : { type : 'date-time' },
doneAt : { type : 'date-time' }
}
};
}
}
module.exports = LoadAttachments;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class LoadCategories extends Model {
static get tableName() { return 'load_categories'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['load_id','category_id'],
properties : {
load_id : { type : 'integer', minimum : 0 },
category_id : { type : 'integer', minimum : 0 },
}
};
}
}
module.exports = LoadCategories;

View File

@@ -1,60 +0,0 @@
'use strict';
const { Model } = require('objection');
class Loads extends Model {
static get tableName() { return 'loads'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'company_id',
'responsible_id',
'origin_country',
'origin_state',
'origin_city',
'origin_zipcode',
'origin_address_line1',
'destination_address_line1',
'updatedAt',
'createdAt',
'publication_status'
],
properties : {
company_id : { type : 'integer' , minimum : 0 },
responsible_id : { type : 'integer' , minimum : 0 },
truck_type : { type : 'string' , maxLength : 100 },
origin_country : { type : 'string' , default : 'Mexico', maxLength : 45 },
origin_state : { type : 'string' , maxLength : 45 },
origin_city : { type : 'string' , maxLength : 45 },
origin_zipcode : { type : 'string' , maxLength : 10 },
origin_lat : { type : 'string' , maxLength : 45 },
origin_lng : { type : 'string' , maxLength : 45 },
origin_address_line1 : { type : 'string' , maxLength : 100 },
origin_address_line2 : { type : 'string' , maxLength : 100 },
destination_country : { type : 'string' , maxLength : 45 },
destination_state : { type : 'string' , maxLength : 45 },
destination_city : { type : 'string' , maxLength : 45 },
destination_zipcode : { type : 'string' , maxLength : 10 },
destination_lat : { type : 'string' , maxLength : 45 },
destination_lng : { type : 'string' , maxLength : 45 },
destination_address_line1 : { type : 'string' , maxLength : 100 },
destination_address_line2 : { type : 'string' , maxLength : 100 },
weight : { type : 'number' , minimum : 0.0 },
est_loading_date : { type : 'date-time' },
est_unloading_date : { type : 'date-time' },
notes : { type : 'string', maxLength : 256 },
updatedAt : { type : 'date-time' },
createdAt : { type : 'date-time' },
publishedAt : { type : 'date-time' },
loadedAt : { type : 'date-time' },
transitAt : { type : 'date-time' },
deliveredAt : { type : 'date-time' },
publication_status : { type : 'string' , default : 'Draft', enum : ['Draft', 'Published', 'Completed', 'Closed'] },
status : { type : 'string', enum : ['Published', 'Loading', 'Transit', 'Downloading', 'Delivered'] }
}
};
}
}
module.exports = Loads;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class LocationCategories extends Model {
static get tableName() { return 'location_categories'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['location_id','category_id'],
properties : {
location_id : { type : 'integer', minimum : 0 },
category_id : { type : 'integer', minimum : 0 },
}
};
}
}
module.exports = LocationCategories;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class LocationTruckTypes extends Model {
static get tableName() { return 'location_truck_types'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['location_id','truck_type_id'],
properties : {
location_id : { type : 'integer', minimum : 0 },
truck_type_id : { type : 'integer', minimum : 0 },
}
};
}
}
module.exports = LocationTruckTypes;

View File

@@ -1,25 +0,0 @@
'use strict';
const { Model } = require('objection');
class Locations extends Model {
static get tableName() { return 'locations'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['company_id','type','state','city','country','zipcode','address_line1'],
properties : {
company_id : { type : 'integer', minimum : 0 },
type : { type : 'string' , enum : ['loading', 'unloading', 'both'] },
state : { type : 'string', maxLength : 45 },
city : { type : 'string', maxLength : 45 },
country : { type : 'string', maxLength : 45 },
zipcode : { type : 'string', maxLength : 10 },
address_line1 : { type : 'string', maxLength : 100 },
address_line2 : { type : 'string', maxLength : 100 }
}
};
}
}
module.exports = Locations;

View File

@@ -1,18 +0,0 @@
'use strict';
const { Model } = require('objection');
class MetadataCategories extends Model {
static get tableName() { return 'metadata_categories'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['category'],
properties : {
category: { type: 'string', maxLength: 100 },
}
};
}
}
module.exports = MetadataCategories;

View File

@@ -1,21 +0,0 @@
'use strict';
const { Model } = require('objection');
class MetadataCities extends Model {
static get tableName() { return 'metadata_cities'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['city','state','country'],
properties : {
city: { type: 'string', maxLength: 100 },
state: { type: 'string', maxLength: 100 },
country: { type: 'string', maxLength: 100 },
zipcode: { type: 'string', maxLength: 100 },
}
};
}
}
module.exports = MetadataCities;

View File

@@ -1,18 +0,0 @@
'use strict';
const { Model } = require('objection');
class MetadataProducts extends Model {
static get tableName() { return 'metadata_products'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['type'],
properties : {
product: { type: 'string', maxLength: 100 },
}
};
}
}
module.exports = MetadataProducts;

View File

@@ -1,18 +0,0 @@
'use strict';
const { Model } = require('objection');
class MetadataTruckTypes extends Model {
static get tableName() { return 'metadata_truck_types'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['type'],
properties : {
truck_type: { type: 'string', maxLength: 100 },
}
};
}
}
module.exports = MetadataTruckTypes;

View File

@@ -1,36 +0,0 @@
'use strict';
const { Model } = require('objection');
class Users extends Model {
static get tableName() { return 'users'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'email',
'password',
'name',
'last_name',
'job_role',
'permissions',
'createdAt',
'is_active'
],
properties : {
company_id : { type : 'integer' , minimum : 0 },
phone: { type: 'string' , maxLength : 45 },
email : { type : 'string' , maxLength : 254 },
password : { type : 'string' , maxLength : 64 },
name : { type : 'string' , maxLength : 45 },
last_name : { type : 'string', maxLength : 100 },
job_role: { type: 'string', default : 'limited', enum : ['owner', 'manager', 'staff', 'driver', 'limited'] },
permissions: { type: 'string', default: 'limited', enum : ['carrier', 'shipper', 'limited'] },
createdAt: { type : 'date-time' },
is_active : { type : 'boolean', default : true }
}
};
}
}
module.exports = Users;

View File

@@ -1,20 +0,0 @@
'use strict';
const { Model } = require('objection');
class UserSessions extends Model {
static get tableName() { return 'user_sessions'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['user_id','token','expiration'],
properties : {
user_id : { type : 'integer' , minimum : 0 },
token: { type: 'string' , maxLength : 256 },
expiration: { type: 'string' },
}
};
}
}
module.exports = UserSessions;

View File

@@ -1,36 +0,0 @@
'use strict';
const { Model } = require('objection');
class Users extends Model {
static get tableName() { return 'users'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'email',
'password',
'name',
'last_name',
'job_role',
'permissions',
'createdAt',
'is_active'
],
properties : {
company_id : { type : 'integer' , minimum : 0 },
phone: { type: 'string' , maxLength : 45 },
email : { type : 'string' , maxLength : 254 },
password : { type : 'string' , maxLength : 64 },
name : { type : 'string' , maxLength : 45 },
last_name : { type : 'string', maxLength : 100 },
job_role: { type: 'string', default : 'limited', enum : ['owner', 'manager', 'staff', 'driver', 'warehouse', 'limited'] },
permissions: { type: 'string', default: 'limited', enum : ['carrier', 'shipper', 'limited'] },
createdAt: { type: "string" },
is_active : { type : 'boolean', default : true }
}
};
}
}
module.exports = Users;

View File

@@ -1,32 +0,0 @@
'use strict';
const { Model } = require('objection');
class VehiclePublications extends Model {
static get tableName() { return 'vehicle_publications'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'vehicle_id',
'published_by',
'published_date',
'destination',
'is_available',
'is_hidden',
],
properties : {
vehicle_id : { type : 'integer' , minimum : 0 },
published_by : { type : 'integer' , minimum : 0 },
published_date : { type : 'date-time' },
destination : { type : 'string' , maxLength : 45 },
is_available : { type: 'boolean' , default : false },
is_hidden : { type: 'boolean' , default : true },
available_date : { type : 'date-time' },
notes : { type: 'string' , maxLength : 256 },
}
};
}
}
module.exports = VehiclePublications;

View File

@@ -1,19 +0,0 @@
'use strict';
const { Model } = require('objection');
class LoadCategories extends Model {
static get tableName() { return 'vehicle_categories'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : ['vehicle_id','category_id'],
properties : {
vehicle_id : { type : 'integer', minimum : 0 },
category_id : { type : 'integer', minimum : 0 },
}
};
}
}
module.exports = LoadCategories;

View File

@@ -1,34 +0,0 @@
'use strict';
const { Model } = require('objection');
class Vehicles extends Model {
static get tableName() { return 'vehicles'; }
static get idColumn() { return 'id'; }
static get jsonSchema() {
return {
type : 'object',
required : [
'company_id',
'background_tracking',
'status',
'createdAt'
],
properties : {
company_id : { type : 'integer' , minimum : 0 },
VIN : { type: 'string', maxLength : 20 },
circulation_serial_number : { type: 'string', maxLength : 100 },
truck_type : { type: 'string', maxLength : 100 },
background_tracking: { type: 'boolean' , default : false },
status: { type: 'string', default : 'Free', enum : ['Free', 'Loading', 'Transit', 'Downloading'] },
last_location_lat : { type: 'string', maxLength : 45 },
last_location_lng : { type: 'string', maxLength : 45 },
last_location_time : { type : 'date-time' },
active_load : { type : 'integer' , minimum : 0 },
active_driver : { type : 'integer' , minimum : 0 },
createdAt: { type : 'date-time' },
}
};
}
}
module.exports = Vehicles;

View File

@@ -1,38 +0,0 @@
'use strict';
class SharedResources{
constructor(){
this.dictionary = {};
}
set( key , val ){
this.dictionary[ key ] = val;
}
exists( key ){
return ( this.dictionary[ key ] != undefined );
}
get( key ){
if( ! this.exists( key ) ){
throw new Error( `Key [${key}] not defined!` );
}
return this.dictionary[ key ];
}
try_get( key ){
if( ! this.exists( key ) ){
return null;
}else{
return this.dictionary[ key ];
}
}
remove( key ){
if( this.exists(key) ){
delete this.dictionary[ key ];
}
}
}
module.exports = new SharedResources();

View File

@@ -1,12 +0,0 @@
"use strict";
const crypto = require('crypto');
/**
* Convert string to sha256 string in hex
* @param {*} text
* @returns
*/
function toSha256( text ){
return crypto.createHmac( "sha256" , "" ).update( text ).digest( 'hex' );
}
module.exports = { toSha256 };

View File

@@ -1,69 +0,0 @@
'use strict';
const apiConfig = require( '../../../config/apiConfig.json' );
const Knex = require('knex');
const { Model } = require('objection');
const UNINIT = 0;
const INIT = 1;
const ONLINE = 2;
const OFFLINE = 3;
class SystemServices {
constructor(){
this.SystemServiceState = UNINIT;
}
async setup(){
this.SystemServiceState = UNINIT;
}
async init(){
this.SystemServiceState = INIT;
}
async connect(){
const knex = Knex({
client: 'mysql',
useNullAsDefault: true,
connection: {
host: apiConfig.sql.host,
port: apiConfig.sql.port,
user: apiConfig.sql.user,
password: apiConfig.sql.password,
database: apiConfig.sql.database,
}
});
Model.knex(knex);
this.knex = knex;
console.log("Connected to SQL");
this.SystemServiceState = ONLINE;
}
async disconnect(){
this.knex.destroy();
console.log("SQL Disconnected");
this.SystemServiceState = OFFLINE;
}
async deinit(){
this.SystemServiceState = UNINIT;
}
async getState(){
switch( this.SystemServiceState ){
case UNINIT:
return "UNINIT";
case INIT:
return "INIT";
case ONLINE:
return "ONLINE";
case OFFLINE:
return "OFFLINE";
default:
return "UNINIT";
}
}
}
module.exports = new SystemServices();

View File

@@ -1,140 +0,0 @@
'use strict';
/**
* ExpressJS Controller
*/
require('dotenv').config();
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');
/// Import Applications to serve
const AppsController = require('../../Controller');
const middlewares = require('./middlewares');
const UNINIT = 0;
const INIT = 1;
const ONLINE = 2;
const OFFLINE = 3;
class ExpressJSServices {
constructor(){
this.SystemServiceState = UNINIT;
this.serverPort = process.env.SERVER_PORT || 3000;
this.app = express();
}
async setup(){
const app = this.app;
await AppsController.setup();
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']
}));
this.SystemServiceState = UNINIT;
}
async init(){
const app = this.app;
await AppsController.init();
app.use( middlewares.errorJSON );
app.use( AppsController.app_controller() );
app.use( middlewares.error404 );
this.SystemServiceState = INIT;
}
async connect(){
const app = this.app;
const serverPort = this.serverPort;
await AppsController.connect();
const server = app.listen( serverPort , function(err){
if( !err ){
console.log('API listen on port', serverPort );
}else{
console.log( err );
}
});
this.server = server;
this.SystemServiceState = ONLINE;
}
async disconnect(){
await AppsController.disconnect();
this.server.close();
this.SystemServiceState = OFFLINE;
}
async deinit(){
await AppsController.deinit();
this.SystemServiceState = UNINIT;
}
async getState(){
switch( this.SystemServiceState ){
case UNINIT:
return "UNINIT";
case INIT:
return "INIT";
case ONLINE:
return "ONLINE";
case OFFLINE:
return "OFFLINE";
default:
return "UNINIT";
}
}
}
module.exports = new ExpressJSServices();

View File

@@ -1,87 +0,0 @@
'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,
};

View File

@@ -1,84 +0,0 @@
'user strict';
const apiConfig = require( '../../../../config/apiConfig.json' );
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

@@ -1,22 +0,0 @@
'user strict';
const nodemailer = require("nodemailer");
const apiConfig = require( '../../../../config/apiConfig.json' );
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

@@ -1,36 +0,0 @@
'use strict';
const { StandAloneContactEmail } = require('./StandAlone.handler');
const { AccountVerifyEmail, AccountConfirmed, AccountPwdResetEmail, ContactEmail } = require('./SendGrid.handler');
async function onChecksumGeneration( data ){
console.log( data );
const receiver = data.email;
await AccountVerifyEmail( receiver, data );
}
async function onAccountConfirmed( data ){
const receiver = data.email;
await AccountConfirmed( receiver, data );
}
async function onPasswordReset( data ){
const receiver = data.email;
await AccountPwdResetEmail( receiver, data );
}
async function onContactFromWebPage( data ){
const receiver = data.email;
await StandAloneContactEmail( data );
await ContactEmail( receiver, data );
}
/**
* Dictionary of event ids and handlers
*/
module.exports = {
"App:Account:getchecksum:signup" : onChecksumGeneration,
"App:Account:signupconfirmed":onAccountConfirmed,
"App:Account:getchecksum:recover":onPasswordReset,
"App:ContactEmail:contact":onContactFromWebPage,
};

View File

@@ -1,71 +0,0 @@
'use strict';
const events = require('events');
const SharedResources = require('../../Shared/Resources');
const resources_list = require('./resources');
const UNINIT = 0;
const INIT = 1;
const ONLINE = 2;
const OFFLINE = 3;
class SystemServices {
constructor(){
this.resources = resources_list;
this.eventEmitter = new events.EventEmitter();
this.SystemServiceState = UNINIT;
}
addEvent( event_id , callback ){
this.eventEmitter.on( event_id , callback );
}
publishEvent( event_id , data ){
this.eventEmitter.emit( event_id , data );
}
async setup(){
for ( const resource of this.resources ){
for ( const [key, value] of Object.entries( resource ) ) {
this.eventEmitter.on( key , value );
}
}
this.SystemServiceState = UNINIT;
}
async init(){
SharedResources.set( "SysS:EventManager" , this );
this.SystemServiceState = INIT;
}
async connect(){
this.SystemServiceState = ONLINE;
}
async disconnect(){
this.SystemServiceState = OFFLINE;
}
async deinit(){
this.SystemServiceState = UNINIT;
}
async getState(){
switch( this.SystemServiceState ){
case UNINIT:
return "UNINIT";
case INIT:
return "INIT";
case ONLINE:
return "ONLINE";
case OFFLINE:
return "OFFLINE";
default:
return "UNINIT";
}
}
}
module.exports = new SystemServices();

View File

@@ -1,7 +0,0 @@
'use strict';
const EmailEvents = require('./EmailEvents');
module.exports = [
EmailEvents,
];

View File

@@ -1,33 +0,0 @@
'use strict';
const UNINIT = 0;
const INIT = 1;
const ONLINE = 2;
const OFFLINE = 3;
class SystemService {
constructor(){
this.SystemServiceState = UNINIT;
}
async setup(){
this.SystemServiceState = UNINIT;
}
async init(){
this.SystemServiceState = INIT;
}
async connect(){
this.SystemServiceState = ONLINE;
}
async disconnect(){
this.SystemServiceState = OFFLINE;
}
async deinit(){
this.SystemServiceState = UNINIT;
}
}
module.exports = SystemService;

View File

@@ -1,78 +0,0 @@
'use strict';
const EventManager = require('./EventManager');
const Connections = require('./Connections');
const Controller = require('./Controller');
const UNINIT = 0;
const INIT = 1;
const ONLINE = 2;
const OFFLINE = 3;
class SystemServices {
constructor(){
this.resources = [
EventManager,
Connections,
Controller,
];
this.SystemServiceState = UNINIT;
}
async setup(){
for ( const service of this.resources ){
await service.setup();
}
this.SystemServiceState = UNINIT;
}
async init(){
for ( const service of this.resources ){
await service.init();
}
this.SystemServiceState = INIT;
}
async connect(){
for ( const service of this.resources ){
await service.connect();
}
this.SystemServiceState = ONLINE;
}
async disconnect(){
for ( const service of this.resources ){
await service.disconnect();
}
this.SystemServiceState = OFFLINE;
}
async deinit(){
for ( const service of this.resources ){
await service.deinit();
}
this.SystemServiceState = UNINIT;
}
async getState(){
switch( this.SystemServiceState ){
case UNINIT:
return "UNINIT";
case INIT:
return "INIT";
case ONLINE:
return "ONLINE";
case OFFLINE:
return "OFFLINE";
default:
return "UNINIT";
}
}
}
module.exports = new SystemServices();

View File

@@ -1,32 +0,0 @@
'use strict';
const process = require('node:process');
const SystemServices = require('./SysS');
async function main(){
await SystemServices.setup();
await SystemServices.init();
await SystemServices.connect();
}
main()
.then( ( out ) => { if( out ){ console.log( out ); } } )
.catch( ( error ) => { if( error ){ console.error( error ); } } );
async function disconnect_server( code ){
if( await SystemServices.getState() !== "OFFLINE" ){
await SystemServices.disconnect();
console.log("Server disconnected with exit code : " , code );
}
}
process.on('SIGINT', () => {
disconnect_server( "SIGINT" ).then( ()=>{} ).catch((error)=>{
console.error("Shutdown error", error);
})
} );
process.on('exit', (code) => {
disconnect_server( "exit:"+code ).then( ()=>{} ).catch((error)=>{
console.error("Shutdown error", error);
})
} );

View File

@@ -1,18 +0,0 @@
"use strict";
/// Unit testing dependences
const assert = require("assert");
const SharedResources = require('../src/Shared/SharedResources');
describe('Shared Resources' , () => {
it('Check key', async () => {
assert.equal( SharedResources.exists("test") , false );
SharedResources.set("test" , "This is a test value ");
assert.equal( SharedResources.exists("test") , true );
SharedResources.remove("test");
assert.equal( SharedResources.exists("test") , false );
})
});