feat(GraphQL): Initial revision of APIv2 GraphQL
This commit is contained in:
@@ -27,10 +27,13 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.19.2",
|
||||||
"express-fileupload": "^1.4.1",
|
"express-fileupload": "^1.4.1",
|
||||||
"express-jwt": "^8.4.1",
|
"express-jwt": "^8.4.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
"graphql": "^16.9.0",
|
||||||
|
"graphql-http": "^1.22.1",
|
||||||
|
"graphql-scalars": "^1.23.0",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
const { DateResolver, DateTimeResolver } = require('graphql-scalars');
|
||||||
|
const { Account, User, Company } = require('../../Domain');
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// Queries
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
async function account( args, context ) {
|
||||||
|
const account = new Account( context.graphQLContext.userId );
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function profile( args, context ) {
|
||||||
|
const profile = new User( context.graphQLContext.userId );
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function company( args, context ) {
|
||||||
|
const company = new Company( context.graphQLContext.companyId );
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// Mutations
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
account,
|
||||||
|
profile,
|
||||||
|
company
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
type Query {
|
||||||
|
account : Account!
|
||||||
|
profile : User!
|
||||||
|
company : Company!
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar DateTime
|
||||||
|
|
||||||
|
type Session {
|
||||||
|
token : String!
|
||||||
|
expiration : DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account {
|
||||||
|
user : User!
|
||||||
|
|
||||||
|
sessions( limit: Int , offset : Int ) : [Session]!
|
||||||
|
sessionsCount : Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationCategory{
|
||||||
|
id : Int!
|
||||||
|
category : String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Location{
|
||||||
|
id : Int!
|
||||||
|
company : Company!
|
||||||
|
type : String!
|
||||||
|
state : String!
|
||||||
|
city : String!
|
||||||
|
country : String!
|
||||||
|
zipcode : String!
|
||||||
|
address_line1 : String!
|
||||||
|
address_line2 : String
|
||||||
|
|
||||||
|
categories : [LocationCategory]!
|
||||||
|
categoriesCount : Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TruckType{
|
||||||
|
id : Int!
|
||||||
|
category : String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompanyCategory{
|
||||||
|
id : Int!
|
||||||
|
category : String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Company {
|
||||||
|
id : Int!
|
||||||
|
owner : User!
|
||||||
|
type : String!
|
||||||
|
is_hidden : Boolean!
|
||||||
|
is_active : Boolean!
|
||||||
|
|
||||||
|
name : String!
|
||||||
|
description : String
|
||||||
|
|
||||||
|
createdAt : DateTime!
|
||||||
|
|
||||||
|
locations( limit: Int , offset : Int ) : [Location]!
|
||||||
|
locationsCount : Int!
|
||||||
|
|
||||||
|
categories : [LocationCategory]!
|
||||||
|
categoriesCount : Int!
|
||||||
|
|
||||||
|
truck_types : [TruckType]!
|
||||||
|
truck_typesCount : Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id : Int!
|
||||||
|
company : Company
|
||||||
|
phone : String
|
||||||
|
|
||||||
|
email : String!
|
||||||
|
name : String!
|
||||||
|
last_name : String!
|
||||||
|
job_role : String!
|
||||||
|
permissions : String!
|
||||||
|
createdAt : DateTime!
|
||||||
|
is_active : Boolean!
|
||||||
|
|
||||||
|
locations( limit: Int , offset : Int ) : [Location]!
|
||||||
|
locationsCount : Int!
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
'use strict';
|
||||||
|
const fs = require('fs');
|
||||||
|
const { join } = require('path');
|
||||||
|
const { buildSchema } = require('graphql');
|
||||||
|
|
||||||
|
const schema = fs.readFileSync(join(__dirname, './schema.graphql'), 'utf8');
|
||||||
|
|
||||||
|
module.exports = buildSchema( schema );
|
||||||
26
v2/server/src/Apps/PrivateResources/Controller/index.js
Normal file
26
v2/server/src/Apps/PrivateResources/Controller/index.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
const router = require('express').Router();
|
||||||
|
|
||||||
|
const { createHandler } = require("graphql-http/lib/use/express");
|
||||||
|
|
||||||
|
/// Include graphql schema and resolvers
|
||||||
|
const schemaDescription = require('./graphql/schema.js');
|
||||||
|
const schemaResolvers = require('./graphql/resolvers.js');
|
||||||
|
|
||||||
|
router.get('/test', async (req, res) => {
|
||||||
|
console.log( req.graphQLContext );
|
||||||
|
res.status(200).send({
|
||||||
|
msg : "It is alive!"
|
||||||
|
});
|
||||||
|
} );
|
||||||
|
|
||||||
|
router.post( '/graphql',
|
||||||
|
createHandler({
|
||||||
|
schema: schemaDescription,
|
||||||
|
rootValue : schemaResolvers,
|
||||||
|
context: async (req, params) => { return { graphQLContext : req.raw.graphQLContext }; },
|
||||||
|
graphiql: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
179
v2/server/src/Apps/PrivateResources/Domain/index.js
Normal file
179
v2/server/src/Apps/PrivateResources/Domain/index.js
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Repository = require('../Repository');
|
||||||
|
|
||||||
|
class Company {
|
||||||
|
constructor( companyId , owner = null ){
|
||||||
|
this._companyId = companyId;
|
||||||
|
this._company = null;
|
||||||
|
this._owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
async populate_content(){
|
||||||
|
if(! this._company ){
|
||||||
|
this._company = await Repository.getCompanyById( this._companyId );
|
||||||
|
}
|
||||||
|
if(! this._owner ){
|
||||||
|
this._owner = new User( this._company.owner_id, this );
|
||||||
|
await this._owner.populate_content();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async id(){
|
||||||
|
return this._companyId;
|
||||||
|
}
|
||||||
|
async owner(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
async type(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.type;
|
||||||
|
}
|
||||||
|
async is_hidden(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.is_hidden;
|
||||||
|
}
|
||||||
|
async is_active(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.is_active;
|
||||||
|
}
|
||||||
|
async name(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.name;
|
||||||
|
}
|
||||||
|
async description(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.description;
|
||||||
|
}
|
||||||
|
async createdAt(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company.createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// locations( limit: Int , offset : Int ) : [Location]!
|
||||||
|
// locationsCount : Int!
|
||||||
|
// categories : [LocationCategory]!
|
||||||
|
// categoriesCount : Int!
|
||||||
|
// truck_types : [TruckType]!
|
||||||
|
// truck_typesCount : Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
constructor( userId, company = null ){
|
||||||
|
this._userId = userId;
|
||||||
|
this._user = null;
|
||||||
|
|
||||||
|
this._company = company;
|
||||||
|
}
|
||||||
|
|
||||||
|
async populate_content(){
|
||||||
|
if(! this._user ){
|
||||||
|
this._user = await Repository.getUserById( this._userId );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( (!this._company) && (this._user.company_id) ){
|
||||||
|
/// Populate only if company is linked to user
|
||||||
|
this._company = new Company( this._user.company_id, this );
|
||||||
|
await this._company.populate_content();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async id(){
|
||||||
|
return this._userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async company(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._company;
|
||||||
|
}
|
||||||
|
async phone(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
async email(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.email;
|
||||||
|
}
|
||||||
|
async name(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.name;
|
||||||
|
}
|
||||||
|
async last_name(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.last_name;
|
||||||
|
}
|
||||||
|
async job_role(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.job_role;
|
||||||
|
}
|
||||||
|
async permissions(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.permissions;
|
||||||
|
}
|
||||||
|
async createdAt(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.createdAt;
|
||||||
|
}
|
||||||
|
async is_active(){
|
||||||
|
await this.populate_content();
|
||||||
|
return this._user.is_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
async locations(){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async locationsCount(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Account {
|
||||||
|
constructor( userId ){
|
||||||
|
this._userId = userId;
|
||||||
|
this._sessions = null;
|
||||||
|
this._user = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async user(){
|
||||||
|
if( this._user ){
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._user = new User( this._userId );
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _update_sessions(){
|
||||||
|
if( this._sessions ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sessions = (await Repository.getSessions( this._userId )).map( (item) => {
|
||||||
|
return {
|
||||||
|
token : item.token,
|
||||||
|
expiration : item.expiration
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
async sessions(){
|
||||||
|
await this._update_sessions();
|
||||||
|
return this._sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sessionsCount(){
|
||||||
|
await this._update_sessions();
|
||||||
|
return this._sessions.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Account,
|
||||||
|
User,
|
||||||
|
Company
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
'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 getUserById( userId ){
|
||||||
|
return await Users.query().findById( userId );
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompanyById( companyId ){
|
||||||
|
return await Companies.query().findById( companyId );
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSessions( userId ){
|
||||||
|
return await Sessions.query().where("user_id","=",userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SpecificModelRepository();
|
||||||
5
v2/server/src/Apps/PrivateResources/Repository/index.js
Normal file
5
v2/server/src/Apps/PrivateResources/Repository/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const SpecificModelRepository = require('./Objection');
|
||||||
|
|
||||||
|
module.exports = SpecificModelRepository;
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
const middlewares = require('./middlewares');
|
||||||
|
|
||||||
const account = require('../Apps/Account/Controller');
|
const account = require('../Apps/Account/Controller');
|
||||||
|
const privateResources = require('../Apps/PrivateResources/Controller');
|
||||||
|
|
||||||
app.use('/account', account);
|
app.use('/account', account);
|
||||||
|
|
||||||
|
app.use( middlewares.jwtValidator );
|
||||||
|
app.use( middlewares.contextGenerator );
|
||||||
|
|
||||||
|
app.use('/private', privateResources);
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|||||||
54
v2/server/src/Controller/middlewares.js
Normal file
54
v2/server/src/Controller/middlewares.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const apiConfig = require( '../../config/apiConfig.json' );
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const jwtSecret = apiConfig.authentication.jwtSecret;
|
||||||
|
|
||||||
|
const { getModel } = require('../Shared/Models/Objection');
|
||||||
|
const Users = getModel('users');
|
||||||
|
|
||||||
|
function jwtValidator(req, res, next){
|
||||||
|
if( req.JWT ){
|
||||||
|
req.JWT.isValid = false;
|
||||||
|
try{
|
||||||
|
req.JWT.payload = jwt.verify( req.JWT.raw, jwtSecret );
|
||||||
|
if( !req.JWT.payload ){
|
||||||
|
return res.status(401).send({error:"Unauthorized",code:401});
|
||||||
|
}else{
|
||||||
|
req.JWT.isValid = true;
|
||||||
|
}
|
||||||
|
}catch( err ){
|
||||||
|
console.error( err );
|
||||||
|
return res.status(401).send({error:"Unauthorized",code:401});
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}else{
|
||||||
|
return res.status(401).send({error:"Unauthorized",code:401});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function contextGenerator( req, res, next ){
|
||||||
|
if( ! req.JWT?.isValid ){
|
||||||
|
return res.status(401).send({error:"Unauthorized",code:401});
|
||||||
|
}
|
||||||
|
const userId = req.JWT.payload.sub;
|
||||||
|
const user = await Users.query().findById( userId ).select( "id", "company_id", "phone", "email", "name", "last_name", "job_role", "permissions", "createdAt", "is_active" );
|
||||||
|
const companyId = user.company_id;
|
||||||
|
const job_role = user.job_role;
|
||||||
|
const permissions = user.permissions;
|
||||||
|
|
||||||
|
req.graphQLContext = {
|
||||||
|
userId,
|
||||||
|
companyId,
|
||||||
|
job_role,
|
||||||
|
permissions,
|
||||||
|
user
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
jwtValidator,
|
||||||
|
contextGenerator
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user