feat(GraphQL): Initial revision of APIv2 GraphQL

This commit is contained in:
Josepablo C
2024-08-06 03:59:11 -06:00
parent 15abfe6c45
commit ae920ca2c7
13 changed files with 439 additions and 3 deletions

View File

@@ -27,10 +27,13 @@
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express": "^4.19.2",
"express-fileupload": "^1.4.1",
"express-jwt": "^8.4.1",
"form-data": "^4.0.0",
"graphql": "^16.9.0",
"graphql-http": "^1.22.1",
"graphql-scalars": "^1.23.0",
"helmet": "^7.0.0",
"jsonschema": "^1.4.1",
"jsonwebtoken": "^9.0.2",

View File

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

View File

@@ -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!
}

View File

@@ -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 );

View 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;

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

@@ -1,8 +1,16 @@
const express = require('express');
const app = express();
const middlewares = require('./middlewares');
const account = require('../Apps/Account/Controller');
const privateResources = require('../Apps/PrivateResources/Controller');
app.use('/account', account);
app.use( middlewares.jwtValidator );
app.use( middlewares.contextGenerator );
app.use('/private', privateResources);
module.exports = app;

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