diff --git a/package.json b/package.json index edb4198..399d07c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "node src/", "dev": "nodemon src/", - "test":"mocha test/lib/handlers/proposals" + "dev3": "nodemon server/src/", + "test": "mocha test/lib/handlers/proposals" }, "repository": { "type": "git", @@ -38,10 +39,11 @@ "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.2", + "objection": "^3.1.4", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..3af4002 --- /dev/null +++ b/server/README.md @@ -0,0 +1,5 @@ +# REST API Framework + +## Architecture Design + +![alt text](docs/assets/APIDesign.png) \ No newline at end of file diff --git a/server/config/apiConfig.json b/server/config/apiConfig.json new file mode 100644 index 0000000..606c680 --- /dev/null +++ b/server/config/apiConfig.json @@ -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" + } +} diff --git a/server/docs/assets/APIDesign.png b/server/docs/assets/APIDesign.png new file mode 100644 index 0000000..de582c8 Binary files /dev/null and b/server/docs/assets/APIDesign.png differ diff --git a/server/src/Apps/Account/Controller/index.js b/server/src/Apps/Account/Controller/index.js new file mode 100644 index 0000000..60e4312 --- /dev/null +++ b/server/src/Apps/Account/Controller/index.js @@ -0,0 +1,60 @@ +'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', dummy_middleware ); + +router.get('/authorize/:session_token', dummy_middleware ); + +router.get('/check-account/:email', async( req, res ) => { + try{ + const email = req.params.email; + const data = await Application.check_account( email ); + return res.send( data ); + }catch(error){ + console.error( error ); + } +} ); + +router.post('/signup', async(req,res) => { + try{ + const email = req.body.email; + const password = req.body.password; + const data = await Application.getVerifyChecksum( 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 ); + } +} ); +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 ); + } +} ); + +router.post('/recover', dummy_middleware ); +router.patch('/recover', dummy_middleware ); + +module.exports = router; diff --git a/server/src/Apps/Account/Domain/index.js b/server/src/Apps/Account/Domain/index.js new file mode 100644 index 0000000..42ce032 --- /dev/null +++ b/server/src/Apps/Account/Domain/index.js @@ -0,0 +1,133 @@ +'use strict'; + +const Repository = require('../Repository'); +const jsonwebtoken = require('jsonwebtoken'); +const { toSha256, + publishEvent, + jwtRenewalTimeout, + jwtTimeout, + jwtOptions, + jwtSecret, + tokenSecret, + pwdSecret, + genErrorResponse } = require('../Ports/Interfaces'); + +class ApplicationLogic { + 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 getVerifyChecksum( email , password ){ + const otp = this.genOTP( email ); + const it_exists = await Repository.getByEmail( email ); + + if( it_exists ){ + return genErrorResponse( "Email already exists" ); + } + 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( "getchecksum" , 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 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 + }; + } + +}; + +module.exports = new ApplicationLogic(); diff --git a/server/src/Apps/Account/Ports/Events/index.js b/server/src/Apps/Account/Ports/Events/index.js new file mode 100644 index 0000000..1cc63f6 --- /dev/null +++ b/server/src/Apps/Account/Ports/Events/index.js @@ -0,0 +1,9 @@ +'use strict'; +const App = require('../../App'); + +/** + * Dictionary of event ids and handlers + */ +module.exports = { + // "event_id" : App.onEvent +}; diff --git a/server/src/Apps/Account/Ports/Interfaces/index.js b/server/src/Apps/Account/Ports/Interfaces/index.js new file mode 100644 index 0000000..f1fa5a8 --- /dev/null +++ b/server/src/Apps/Account/Ports/Interfaces/index.js @@ -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 +}; diff --git a/server/src/Apps/Account/Ports/Public/index.js b/server/src/Apps/Account/Ports/Public/index.js new file mode 100644 index 0000000..c15418c --- /dev/null +++ b/server/src/Apps/Account/Ports/Public/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.export = {}; diff --git a/server/src/Apps/Account/Repository/Objection/index.js b/server/src/Apps/Account/Repository/Objection/index.js new file mode 100644 index 0000000..4704304 --- /dev/null +++ b/server/src/Apps/Account/Repository/Objection/index.js @@ -0,0 +1,76 @@ +'use strict'; + +const { getModel } = require('../../../../Shared/Models/Objection'); +const Model = getModel('users'); +const Sessions = getModel('users_sessions'); +const Companies = getModel('companies'); + +class SpecificModelRepository{ + constructor(){} + + async populateCompany( 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 Model.query() + .select( "*" ) + .where("email","=",email).first(); + } + + async createOne( email, safe_password ){ + const user = await Model.query().insert({ + email, + password : safe_password, + "name":"No name", + "last_name":"No lastname", + "createdAt" : new Date().toISOString() + }); + return this.populateCompany( user ); + } + + async findByEmailPassword( email, safe_password ){ + const user = await Model.query().select('*') + .where("email","=",email) + .where("password","=",safe_password) + .first(); + return this.populateCompany( 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 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 ); + } + } + +} + +module.exports = new SpecificModelRepository(); \ No newline at end of file diff --git a/server/src/Apps/Account/Repository/index.js b/server/src/Apps/Account/Repository/index.js new file mode 100644 index 0000000..0c45820 --- /dev/null +++ b/server/src/Apps/Account/Repository/index.js @@ -0,0 +1,5 @@ +'use strict'; + +const SpecificModelRepository = require('./Objection'); + +module.exports = SpecificModelRepository; \ No newline at end of file diff --git a/server/src/Controller/index.js b/server/src/Controller/index.js new file mode 100644 index 0000000..d599c3f --- /dev/null +++ b/server/src/Controller/index.js @@ -0,0 +1,8 @@ +const express = require('express'); +const app = express(); + +const account = require('../Apps/Account/Controller'); + +app.use('/account',account); + +module.exports = app; diff --git a/server/src/Shared/ErrorResponse.js b/server/src/Shared/ErrorResponse.js new file mode 100644 index 0000000..0182dbb --- /dev/null +++ b/server/src/Shared/ErrorResponse.js @@ -0,0 +1,12 @@ +'use strict'; + +function genErrorResponse( msg , code = 400 ){ + return { + error : { + code, + msg + } + } +} + +module.exports = { genErrorResponse }; diff --git a/server/src/Shared/Models/Objection/companies.model.js b/server/src/Shared/Models/Objection/companies.model.js new file mode 100644 index 0000000..3571d59 --- /dev/null +++ b/server/src/Shared/Models/Objection/companies.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/company_categories.model.js b/server/src/Shared/Models/Objection/company_categories.model.js new file mode 100644 index 0000000..ade7e4a --- /dev/null +++ b/server/src/Shared/Models/Objection/company_categories.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/company_truck_types.model.js b/server/src/Shared/Models/Objection/company_truck_types.model.js new file mode 100644 index 0000000..7e6b2cf --- /dev/null +++ b/server/src/Shared/Models/Objection/company_truck_types.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/index.js b/server/src/Shared/Models/Objection/index.js new file mode 100644 index 0000000..0115185 --- /dev/null +++ b/server/src/Shared/Models/Objection/index.js @@ -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 +}; diff --git a/server/src/Shared/Models/Objection/load_attachments.model.js b/server/src/Shared/Models/Objection/load_attachments.model.js new file mode 100644 index 0000000..30b75b9 --- /dev/null +++ b/server/src/Shared/Models/Objection/load_attachments.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/load_categories.model.js b/server/src/Shared/Models/Objection/load_categories.model.js new file mode 100644 index 0000000..15f92b4 --- /dev/null +++ b/server/src/Shared/Models/Objection/load_categories.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/loads.model.js b/server/src/Shared/Models/Objection/loads.model.js new file mode 100644 index 0000000..3234819 --- /dev/null +++ b/server/src/Shared/Models/Objection/loads.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/location_categories.model.js b/server/src/Shared/Models/Objection/location_categories.model.js new file mode 100644 index 0000000..9e66918 --- /dev/null +++ b/server/src/Shared/Models/Objection/location_categories.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/location_truck_types.model.js b/server/src/Shared/Models/Objection/location_truck_types.model.js new file mode 100644 index 0000000..5d91509 --- /dev/null +++ b/server/src/Shared/Models/Objection/location_truck_types.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/locations.model.js b/server/src/Shared/Models/Objection/locations.model.js new file mode 100644 index 0000000..be0945b --- /dev/null +++ b/server/src/Shared/Models/Objection/locations.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/metadata_categories.model.js b/server/src/Shared/Models/Objection/metadata_categories.model.js new file mode 100644 index 0000000..23c26c4 --- /dev/null +++ b/server/src/Shared/Models/Objection/metadata_categories.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/metadata_cities.model.js b/server/src/Shared/Models/Objection/metadata_cities.model.js new file mode 100644 index 0000000..5977ab6 --- /dev/null +++ b/server/src/Shared/Models/Objection/metadata_cities.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/metadata_products.model.js b/server/src/Shared/Models/Objection/metadata_products.model.js new file mode 100644 index 0000000..d6d795d --- /dev/null +++ b/server/src/Shared/Models/Objection/metadata_products.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/metadata_truck_types.model.js b/server/src/Shared/Models/Objection/metadata_truck_types.model.js new file mode 100644 index 0000000..2c7f9be --- /dev/null +++ b/server/src/Shared/Models/Objection/metadata_truck_types.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/user_locations.model.js b/server/src/Shared/Models/Objection/user_locations.model.js new file mode 100644 index 0000000..892d83d --- /dev/null +++ b/server/src/Shared/Models/Objection/user_locations.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/user_sessions.model.js b/server/src/Shared/Models/Objection/user_sessions.model.js new file mode 100644 index 0000000..b384971 --- /dev/null +++ b/server/src/Shared/Models/Objection/user_sessions.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/users.model.js b/server/src/Shared/Models/Objection/users.model.js new file mode 100644 index 0000000..ce1772c --- /dev/null +++ b/server/src/Shared/Models/Objection/users.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/vechicle_publications.model.js b/server/src/Shared/Models/Objection/vechicle_publications.model.js new file mode 100644 index 0000000..ce7666d --- /dev/null +++ b/server/src/Shared/Models/Objection/vechicle_publications.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/vehicle_categories.model.js b/server/src/Shared/Models/Objection/vehicle_categories.model.js new file mode 100644 index 0000000..96cc57f --- /dev/null +++ b/server/src/Shared/Models/Objection/vehicle_categories.model.js @@ -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; diff --git a/server/src/Shared/Models/Objection/vehicles.model.js b/server/src/Shared/Models/Objection/vehicles.model.js new file mode 100644 index 0000000..ecd1474 --- /dev/null +++ b/server/src/Shared/Models/Objection/vehicles.model.js @@ -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; diff --git a/server/src/Shared/Resources/index.js b/server/src/Shared/Resources/index.js new file mode 100644 index 0000000..638a859 --- /dev/null +++ b/server/src/Shared/Resources/index.js @@ -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(); \ No newline at end of file diff --git a/server/src/Shared/ShaUtils.js b/server/src/Shared/ShaUtils.js new file mode 100644 index 0000000..7804f6b --- /dev/null +++ b/server/src/Shared/ShaUtils.js @@ -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 }; diff --git a/server/src/SysS/Connections/index.js b/server/src/SysS/Connections/index.js new file mode 100644 index 0000000..7820292 --- /dev/null +++ b/server/src/SysS/Connections/index.js @@ -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(); diff --git a/server/src/SysS/Controller/index.js b/server/src/SysS/Controller/index.js new file mode 100644 index 0000000..139165b --- /dev/null +++ b/server/src/SysS/Controller/index.js @@ -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(); diff --git a/server/src/SysS/Controller/middlewares.js b/server/src/SysS/Controller/middlewares.js new file mode 100644 index 0000000..a56b25f --- /dev/null +++ b/server/src/SysS/Controller/middlewares.js @@ -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, +}; diff --git a/server/src/SysS/EventManager/EmailEvents/SendGrid.handler.js b/server/src/SysS/EventManager/EmailEvents/SendGrid.handler.js new file mode 100644 index 0000000..f2296d4 --- /dev/null +++ b/server/src/SysS/EventManager/EmailEvents/SendGrid.handler.js @@ -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 }; diff --git a/server/src/SysS/EventManager/EmailEvents/StandAlone.handler.js b/server/src/SysS/EventManager/EmailEvents/StandAlone.handler.js new file mode 100644 index 0000000..f48ce78 --- /dev/null +++ b/server/src/SysS/EventManager/EmailEvents/StandAlone.handler.js @@ -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 }; diff --git a/server/src/SysS/EventManager/EmailEvents/index.js b/server/src/SysS/EventManager/EmailEvents/index.js new file mode 100644 index 0000000..12b1765 --- /dev/null +++ b/server/src/SysS/EventManager/EmailEvents/index.js @@ -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" : onChecksumGeneration, + "App:Account:signupconfirmed":onAccountConfirmed, + "App:Account:getchecksum:pwdreset":onPasswordReset, + "App:ContactEmail:getchecksum":onContactFromWebPage, +}; diff --git a/server/src/SysS/EventManager/index.js b/server/src/SysS/EventManager/index.js new file mode 100644 index 0000000..76d9152 --- /dev/null +++ b/server/src/SysS/EventManager/index.js @@ -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(); diff --git a/server/src/SysS/EventManager/resources.js b/server/src/SysS/EventManager/resources.js new file mode 100644 index 0000000..a488db6 --- /dev/null +++ b/server/src/SysS/EventManager/resources.js @@ -0,0 +1,7 @@ +'use strict'; + +const EmailEvents = require('./EmailEvents'); + +module.exports = [ + EmailEvents, +]; \ No newline at end of file diff --git a/server/src/SysS/Template/index.js b/server/src/SysS/Template/index.js new file mode 100644 index 0000000..f3fa6ec --- /dev/null +++ b/server/src/SysS/Template/index.js @@ -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(); diff --git a/server/src/SysS/index.js b/server/src/SysS/index.js new file mode 100644 index 0000000..4d3904e --- /dev/null +++ b/server/src/SysS/index.js @@ -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(); diff --git a/server/src/index.js b/server/src/index.js new file mode 100644 index 0000000..f15c00e --- /dev/null +++ b/server/src/index.js @@ -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); + }) +} ); diff --git a/server/test/index.js b/server/test/index.js new file mode 100644 index 0000000..126d321 --- /dev/null +++ b/server/test/index.js @@ -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 ); + }) +}); + diff --git a/src/apps/private/vehicles/services.js b/src/apps/private/vehicles/services.js index 9ae4c94..2e7dda0 100644 --- a/src/apps/private/vehicles/services.js +++ b/src/apps/private/vehicles/services.js @@ -1,168 +1,168 @@ -"use strict"; -const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env; -const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` ); -const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` ); -const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` ); -const Model = getModel('vehicles'); - -const populate_list = ['categories', 'active_load','load_shipper','driver']; -const generic = new GenericHandler( Model, null, populate_list ); - -function getAndFilterList( query ){ - const filter_list = []; - const { - categories, - active_load, - load_shipper, - driver, - vehicle_code, - vehicle_name, - vehicle_number, - circulation_serial_number, - truck_type, - tyre_type, - city, - state, - status, - destino - } = query; - - if( categories ) { filter_list.push({ categories }); } - if( active_load ) { filter_list.push({ active_load }); } - if( load_shipper ) { filter_list.push({ load_shipper }); } - if( driver ) { filter_list.push({ driver }); } - if( vehicle_code ) { filter_list.push({ vehicle_code }); } - if( vehicle_name ) { filter_list.push({ vehicle_name }); } - if( vehicle_number ) { filter_list.push({ vehicle_number }); } - if( circulation_serial_number ) { filter_list.push({ circulation_serial_number }); } - if( truck_type ) { filter_list.push({ truck_type }); } - if( tyre_type ) { filter_list.push({ tyre_type }); } - if( city ) { filter_list.push({ city }); } - if( state ) { filter_list.push({ state }); } - if( status ) { filter_list.push({ status }); } - if( destino ) { filter_list.push({ destino }); } - - if( filter_list.length == 0 ){ - return null; - } - return filter_list; -} - -async function findElements( companyId , query ){ - const { page, elements } = getPagination( query ); - const andFilterList = getAndFilterList( query ); - let filter; - if( andFilterList ){ - andFilterList.push({ company : companyId }); - filter = { $and : andFilterList }; - }else{ - filter = { company : companyId }; - } - const { total , limit, skip, data } = await generic.getList( page , elements, filter ); - return { - total, - limit, - skip, - data:data - }; -} - -async function findElementById( elementId , companyId ){ - let retVal = await Model.findById( elementId ).populate( populate_list ) || {}; - return retVal; -} - -const findList = async(req, res) => { - try{ - const query = req.query || {}; - const companyId = req.context.companyId; - const retVal = await findElements( companyId , query ); - res.send( retVal ); - }catch(error){ - console.error( error ); - return res.status( 500 ).send({ error }); - } -}; - -const getById = async(req, res) => { - try{ - const companyId = req.context.companyId; - const elementId = req.params.id; - res.send( await findElementById( elementId , companyId ) ); - }catch(error){ - console.error( error ); - return res.status( 500 ).send({ error }); - } -}; - -const patchVehicle = async(req, res) => { - try{ - const companyId = req.context.companyId; - const elementId = req.params.id; - const permissions = req.context.permissions; - const vehicle = await findElementById( elementId , companyId ); - const data = req.body; - if( !vehicle ){ - throw "You can't modify this vehicle"; - } - if( !data ){ - throw "Vehicle data not sent"; - } - if( permissions !== "role_carrier" ){ - throw "You can't modify vehicles"; - } - data.company = companyId; - await Model.findByIdAndUpdate( elementId , data ); - return res.send( await Model.findById( elementId ) ); - }catch(error){ - console.error( error ); - return res.status( 500 ).send({ error }); - } -}; - -const postVehicle = async(req, res) => { - try{ - const userId = req.context.userId; - const companyId = req.context.companyId; - const permissions = req.context.permissions; - const data = req.body; - if( !data ){ - throw "Vehicle data not sent"; - } - if(permissions !== "role_carrier" ){ - throw "You can't create vehicles"; - } - data.company = companyId; - data.status = "Free"; - data.is_available = false; - data.posted_by = userId; - const vehicle = new Model( data ); - await vehicle.save(); - return res.send( vehicle ); - }catch(error){ - console.error( error ); - return res.status( 500 ).send({ error }); - } -}; - -const deleteVehicle = async(req, res) => { - try{ - const companyId = req.context.companyId; - const elementId = req.params.id; - const permissions = req.context.permissions; - const vehicle = await findElementById( elementId , companyId ); - if( !vehicle ){ - throw "You can't delete this vehicle"; - } - if(permissions !== "role_carrier" ){ - throw "You can't delete vehicles"; - } - await Model.findByIdAndDelete( elementId ); - return res.send(vehicle); - }catch(error){ - console.error( error ); - return res.status( 500 ).send({ error }); - } -}; - -module.exports = { findList, getById, patchVehicle, postVehicle, deleteVehicle }; +"use strict"; +const { ROOT_PATH, LIB_PATH, MODELS_PATH, HANDLERS_PATH } = process.env; +const { getModel } = require( `${ROOT_PATH}/${MODELS_PATH}` ); +const { getPagination } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` ); +const { GenericHandler } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Generic.handler.js` ); +const Model = getModel('vehicles'); + +const populate_list = ['categories', 'active_load','load_shipper','driver']; +const generic = new GenericHandler( Model, null, populate_list ); + +function getAndFilterList( query ){ + const filter_list = []; + const { + categories, + active_load, + load_shipper, + driver, + vehicle_code, + vehicle_name, + vehicle_number, + circulation_serial_number, + truck_type, + tyre_type, + city, + state, + status, + destino + } = query; + + if( categories ) { filter_list.push({ categories }); } + if( active_load ) { filter_list.push({ active_load }); } + if( load_shipper ) { filter_list.push({ load_shipper }); } + if( driver ) { filter_list.push({ driver }); } + if( vehicle_code ) { filter_list.push({ vehicle_code }); } + if( vehicle_name ) { filter_list.push({ vehicle_name }); } + if( vehicle_number ) { filter_list.push({ vehicle_number }); } + if( circulation_serial_number ) { filter_list.push({ circulation_serial_number }); } + if( truck_type ) { filter_list.push({ truck_type }); } + if( tyre_type ) { filter_list.push({ tyre_type }); } + if( city ) { filter_list.push({ city }); } + if( state ) { filter_list.push({ state }); } + if( status ) { filter_list.push({ status }); } + if( destino ) { filter_list.push({ destino }); } + + if( filter_list.length == 0 ){ + return null; + } + return filter_list; +} + +async function findElements( companyId , query ){ + const { page, elements } = getPagination( query ); + const andFilterList = getAndFilterList( query ); + let filter; + if( andFilterList ){ + andFilterList.push({ company : companyId }); + filter = { $and : andFilterList }; + }else{ + filter = { company : companyId }; + } + const { total , limit, skip, data } = await generic.getList( page , elements, filter ); + return { + total, + limit, + skip, + data:data + }; +} + +async function findElementById( elementId , companyId ){ + let retVal = await Model.findById( elementId ).populate( populate_list ) || {}; + return retVal; +} + +const findList = async(req, res) => { + try{ + const query = req.query || {}; + const companyId = req.context.companyId; + const retVal = await findElements( companyId , query ); + res.send( retVal ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const getById = async(req, res) => { + try{ + const companyId = req.context.companyId; + const elementId = req.params.id; + res.send( await findElementById( elementId , companyId ) ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const patchVehicle = async(req, res) => { + try{ + const companyId = req.context.companyId; + const elementId = req.params.id; + const permissions = req.context.permissions; + const vehicle = await findElementById( elementId , companyId ); + const data = req.body; + if( !vehicle ){ + throw "You can't modify this vehicle"; + } + if( !data ){ + throw "Vehicle data not sent"; + } + if( permissions !== "role_carrier" ){ + throw "You can't modify vehicles"; + } + data.company = companyId; + await Model.findByIdAndUpdate( elementId , data ); + return res.send( await Model.findById( elementId ) ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const postVehicle = async(req, res) => { + try{ + const userId = req.context.userId; + const companyId = req.context.companyId; + const permissions = req.context.permissions; + const data = req.body; + if( !data ){ + throw "Vehicle data not sent"; + } + if(permissions !== "role_carrier" ){ + throw "You can't create vehicles"; + } + data.company = companyId; + data.status = "Free"; + data.is_available = false; + data.posted_by = userId; + const vehicle = new Model( data ); + await vehicle.save(); + return res.send( vehicle ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const deleteVehicle = async(req, res) => { + try{ + const companyId = req.context.companyId; + const elementId = req.params.id; + const permissions = req.context.permissions; + const vehicle = await findElementById( elementId , companyId ); + if( !vehicle ){ + throw "You can't delete this vehicle"; + } + if(permissions !== "role_carrier" ){ + throw "You can't delete vehicles"; + } + await Model.findByIdAndDelete( elementId ); + return res.send(vehicle); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +module.exports = { findList, getById, patchVehicle, postVehicle, deleteVehicle };