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

17
v2/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# 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

1
v2/dotenv Normal file
View File

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

53
v2/package.json Normal file
View File

@@ -0,0 +1,53 @@
{
"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.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,93 @@
#! /bin/bash
# Requirements
# Docker: Node v18-alpine
function build_docker(){
if [[ $# -lt 1 ]]; then
echo $0 "[conatiner name]"
return -1
fi
CONTAINER_NAME=$1
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
}

5
v2/server/README.md Normal file
View File

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

View File

@@ -0,0 +1,55 @@
{
"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",
"sql":{
"host":"srv765.hstgr.io",
"port":3306,
"user":"u947463964_sysadmin",
"password":"3^K/47^h5pP",
"database":"u947463964_etaviaporte"
}
}

View File

@@ -0,0 +1,118 @@
<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.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,124 @@
'use strict';
/// Router instance
const router = require('express').Router();
const Application = require('../Domain');
function dummy_middleware( req, res ){
return res.status(500).send({ error:"Not implemented yet" });
}
router.post('/register', dummy_middleware );
router.post('/authorize', async( req, res ) => {
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 } );
}
} );
router.get('/authorize/:session_token', async( req, res ) => {
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 } );
}
} );
router.get('/check-account/:email', async( req, res ) => {
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 } );
}
} );
router.post('/signup', async(req,res) => {
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 } );
}
} );
router.patch('/signup', async(req,res) => {
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 } );
}
} );
router.post('/recover', async(req,res) => {
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 } );
}
} );
router.patch('/recover', async(req,res) => {
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 } );
}
} );
module.exports = router;

View File

@@ -0,0 +1,202 @@
'use strict';
const Repository = require('../Repository');
const jsonwebtoken = require('jsonwebtoken');
const { toSha256,
publishEvent,
jwtRenewalTimeout,
jwtTimeout,
jwtOptions,
jwtSecret,
tokenSecret,
pwdSecret,
genErrorResponse } = require('../Ports/Interfaces');
class Account {
constructor(){
}
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

@@ -0,0 +1,9 @@
'use strict';
const App = require('../../App');
/**
* Dictionary of event ids and handlers
*/
module.exports = {
// "event_id" : App.onEvent
};

View File

@@ -0,0 +1,38 @@
'use strict';
const { toSha256 } = require('../../../../Shared/ShaUtils');
const { authentication } = require('../../../../../config/apiConfig.json');
const { genErrorResponse } = require('../../../../Shared/ErrorResponse');
const SharedResources = require('../../../../Shared/Resources');
function publishEvent( event , data = null ){
const EventBus = SharedResources.get("SysS:EventManager");
const AppEventDomain = "App:Account:"
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,
publishEvent
};

View File

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

View File

@@ -0,0 +1,89 @@
'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();
return await this.populate( user );
}
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 );
return await this.populate( user );
}
}
module.exports = new SpecificModelRepository();

View File

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

View File

@@ -0,0 +1,8 @@
const express = require('express');
const app = express();
const account = require('../Apps/Account/Controller');
app.use('/account',account);
module.exports = app;

View File

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

View File

@@ -0,0 +1,24 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,22 @@
"use strict";
const users = require('./users.model');
const user_sessions = require('./user_sessions.model');
const companies = require('./companies.model');
function getModel( name ){
switch( name ){
case 'users':
return users;
case 'users_sessions':
return user_sessions;
case 'companies':
return companies;
default:
return null;
}
}
module.exports = {
getModel
};

View File

@@ -0,0 +1,32 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,60 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,25 @@
'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 = Users;

View File

@@ -0,0 +1,18 @@
'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

@@ -0,0 +1,21 @@
'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

@@ -0,0 +1,18 @@
'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

@@ -0,0 +1,18 @@
'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

@@ -0,0 +1,36 @@
'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

@@ -0,0 +1,20 @@
'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

@@ -0,0 +1,36 @@
'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: "string" },
is_active : { type : 'boolean', default : true }
}
};
}
}
module.exports = Users;

View File

@@ -0,0 +1,32 @@
'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

@@ -0,0 +1,19 @@
'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

@@ -0,0 +1,34 @@
'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

@@ -0,0 +1,38 @@
'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

@@ -0,0 +1,12 @@
"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

@@ -0,0 +1,68 @@
'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();
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

@@ -0,0 +1,132 @@
'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;
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;
app.use( middlewares.errorJSON );
app.use( AppsController );
app.use( middlewares.error404 );
this.SystemServiceState = INIT;
}
async connect(){
const app = this.app;
const serverPort = this.serverPort;
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(){
this.server.close();
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 ExpressJSServices();

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,
};

View File

@@ -0,0 +1,84 @@
'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

@@ -0,0 +1,22 @@
'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

@@ -0,0 +1,36 @@
'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

@@ -0,0 +1,71 @@
'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

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

View File

@@ -0,0 +1,33 @@
'use strict';
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(){
this.SystemServiceState = ONLINE;
}
async disconnect(){
this.SystemServiceState = OFFLINE;
}
async deinit(){
this.SystemServiceState = UNINIT;
}
}
module.exports = new SystemServices();

View File

@@ -0,0 +1,78 @@
'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();

32
v2/server/src/index.js Normal file
View File

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

18
v2/server/test/index.js Normal file
View File

@@ -0,0 +1,18 @@
"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 );
})
});