From: charleswrayjr Date: Thu, 4 Sep 2025 06:46:13 +0000 (-0500) Subject: Adding necessary api files. X-Git-Url: https://git.phasecustomsoft.com/stylesheets/static/git-logo.png?a=commitdiff_plain;h=1870c917f85a241b82505f616da36d0a4a766440;p=phs-api.git Adding necessary api files. --- diff --git a/.idea/runConfigurations/bin_www.xml b/.idea/runConfigurations/bin_www.xml new file mode 100644 index 0000000..1afb596 --- /dev/null +++ b/.idea/runConfigurations/bin_www.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/api.config.cjs b/api.config.cjs new file mode 100755 index 0000000..caaf5c0 --- /dev/null +++ b/api.config.cjs @@ -0,0 +1,25 @@ +const path = require("path"); + +module.exports = { + apps: [ + { + name: "phs-api", + script: "/usr/src/app/app.js", // Your entry point + instances: 1, + autorestart: true, // THIS is the important part, this will tell PM2 to restart your app if it falls over + max_memory_restart: "3G", + env: { + PHS_ENV: "DEV", + NODE_ENV: "DEVELOPMENT", + }, + env_TEST: { + PHS_ENV: "TEST", + NODE_ENV: "PRODUCTION", + }, + env_PRODUCTION: { + PHS_ENV: "PRODUCTION", + NODE_ENV: "PRODUCTION", + }, + }, + ], +}; diff --git a/app.js b/app.js index d187f73..5c7212e 100644 --- a/app.js +++ b/app.js @@ -1,20 +1,230 @@ -var express = require('express'); -var path = require('path'); -var cookieParser = require('cookie-parser'); -var logger = require('morgan'); +const createError = require('http-errors'); +const express = require('express'); +const compression = require( 'express-compression' ); +const bodyParser = require( 'body-parser' ); +const cookieParser = require('cookie-parser'); +const log4js = require( './src/middleware/loggerMiddleWare' ); +const passport = require( 'passport' ); +let hookJWTStrategy = require( './src/middleware/passport' ); +const cors = require( 'cors' ); +const port = normalizePort( process.env.PORT || '3601' ); +const runOnce = require ( './src/middleware/custom-startup' ); +const Routes = require( './src/routes' ); +const phsdb = require( './src/phsdb' ); +const { Client } = require('ssh2'); +const fs = require('fs'); -var indexRouter = require('./routes/index'); -var usersRouter = require('./routes/users'); +const buffer = require('buffer'); +global.Blob = buffer.Blob -var app = express(); +const app = express(); +const http = require( 'http' ).createServer( app ); -app.use(logger('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); +app.set('etag', false) + +app.use((req, res, next) => { + res.set('Cache-Control', 'no-store'); + next() +}); + +global.phsdb = phsdb; + +// noinspection JSCheckFunctionSignatures +app.use( bodyParser.json( { + limit:'50mb', extended:true +} ) ); +app.use( bodyParser.urlencoded( { + extended:true, + limit: '50mb' +} ) ); app.use(cookieParser()); -app.use(express.static(path.join(__dirname, 'public'))); -app.use('/', indexRouter); -app.use('/users', usersRouter); +//Setting up log4js +const logger = log4js.default; + +// Set log level based on the active environment for global logger +logger.level = 'warn'; + +switch (process.env.PHS_ENV) { + case 'DEV': + logger.level = 'debug'; + break; + case 'UNIT_TEST': + logger.level = 'info'; + break; + default: + break; +} +global.logger = logger; + +// For automatic logging of all requests +app.use( log4js.express ); + +logger.info('PHS_ENV: ' + process.env.PHS_ENV ); +logger.info('NODE_ENV: ' + process.env.NODE_ENV ); +logger.info('Testing log levels: info, debug and error. Only shows for PHS_ENV=DEV' ); +logger.info('Test log level: info' ); // Should show in all NODE_ENV settings +logger.debug('Test log level: debug' ); // Should not show in PRODUCTION + +//Initialize passport +app.use( passport.initialize() ); +hookJWTStrategy( passport ); + +//Run custom startup code once +runOnce().catch( err => logger.error( err ) ); + +//Set express body parsing options. +app.use( compression() ); +app.use( express.json() ); +app.use( express.urlencoded( { extended:false } ) ); +app.use(cookieParser()); + +// 2FA Cors: +app.use(cors({ + exposedHeaders: ['LastFetchDateTime', 'Authentication'], + origin: [ + // Home + 'http://localhost:30005', // local dev + 'http://localhost:23601', + 'http://phs-home:3000', // import / export jobs + 'https://phs-home.phasecustomsoft.com', + 'https://phs-admin.phasecustomsoft.com', + 'https://www.phasecustomsoft.com', + // Customer (local dev) + 'http://localhost:30006', // local dev + 'http://rt2-customer:3000', // import / export jobs + 'https://customer-f.roto-versal.com', + 'https://customer-t.roto-versal.com', + 'https://customer-demo.roto-versal.com', + 'https://customer.roto-versal.com', + // Admin (local dev) + 'http://localhost:8100', + 'http://localhost:30007', + 'https://admin-t.roto-versal.com', + 'https://admin-f.roto-versal.com', + 'https://admin-demo.roto-versal.com', + 'https://admin.roto-versal.com', + // App (local dev) + 'http://localhost:8101', + 'https://app-t.roto-versal.com', + 'https://app-f.roto-versal.com', + 'https://app-demo.roto-versal.com', + 'https://app.roto-versal.com', + // Public website + 'https://www-t.roto-versal.com', + 'https://www.roto-versal.com' + ], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization', 'Origin', 'X-Requested-With', 'Accept', 'Access-Control-Allow-Origin', 'Cookie', 'phs-app-version'], + optionsSuccessResponds: true, +})); + +//Send the current server date/time for each request +app.use( function ( req, res, next ) { + res.setHeader( 'LastFetchDateTime', new Date().toLocaleString() ); + next(); +} ); + +app.use( '/', Routes.APIRoutes( passport ) ); +app.set( 'port', port ); +//Create HTTP Server for App +// noinspection JSCheckFunctionSignatures +http.listen( port ); +http.on( 'error', onError ); +http.on( 'listening', onListening ); + + +process.on('uncaughtException', function(err) { + logger.error( '*************uncaughtException******' ); + logger.error( err ); +}); + +process.on('unhandledRejection', (err) => { + logger.error( '*************unhandledRejection******' ); + logger.error(err.name, err.message); +}); + + +// catch 404 and forward to the error handler +app.use( function ( req, res, next ) { + if (req.url === '/.well-known/appspecific/com.chrome.devtools.json' ) next(); + else { + logger.debug(req.originalUrl) + logger.debug(req.headers['x-forwarded-for']) + next( createError( 404 ) ); + } + +} ); + +function normalizePort( val ) { + const port = parseInt( val, 10 ); + + if (isNaN( port )) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError( error ) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + logger.error( bind + ' requires elevated privileges' ); + process.exit( 1 ); + break; + case 'EADDRINUSE': + logger.error( bind + ' is already in use' ); + process.exit( 1 ); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = http.address(); + const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; + logger.info( 'Listening on ' + bind ); +} + +// error handler +const errorHandler = ( err, req, res, next ) => { + logger.error( '*************errorHandler******' ); + if (err) { + logger.error( err ); + // set locals, only providing error in DEVELOPMENT + res.locals.message = err.message; + res.locals.error = req.app.get( 'env' ) === 'DEVELOPMENT' ? err : {}; + // render the error page + res.status( err.status || 500 ); + res.send(err); + } else next() +}; + +app.use( errorHandler ); + +console.log('running on ' + port); module.exports = app; diff --git a/package.json b/package.json index becff01..ed77ba2 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,31 @@ { "name": "phs-api", - "version": "0.0.0", + "version": "1.0.0", "private": true, "scripts": { - "start": "node ./bin/www" + "start-debug": "NODE_ENV=\"DEVELOPMENT\" nodemon --trace-warnings --inspect=0.0.0.0:9229 app.js" }, "dependencies": { + "body-parser": "^2.2.0", "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "debug": "~2.6.9", - "express": "~4.16.1", - "morgan": "~1.9.1" + "express": "5.0.0", + "express-compression": "^1.0.2", + "http-errors": "~1.6.3", + "jsonwebtoken": "^9.0.2", + "log4js": "^6.9.1", + "morgan": "~1.9.1", + "multer": "^2.0.2", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "pg": "^8.16.3", + "pg-promise": "^11.15.0", + "pug": "3.0.3", + "ssh2": "^1.17.0", + "swagger-ui-express": "^5.0.1" + }, + "devDependencies": { + "jest": "^30.0.5" } } diff --git a/routes/index.js b/routes/index.js deleted file mode 100644 index ecca96a..0000000 --- a/routes/index.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); -}); - -module.exports = router; diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 623e430..0000000 --- a/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET users listing. */ -router.get('/', function(req, res, next) { - res.send('respond with a resource'); -}); - -module.exports = router; diff --git a/run-api.sh b/run-api.sh new file mode 100755 index 0000000..9ddc149 --- /dev/null +++ b/run-api.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +env="" +if [[ ! -z "${PHS_ENV}" ]]; then + env=${PHS_ENV} +fi + +if [ "$env" == "TEST" ]; then + echo "PHS API Test Stack" + npm install; npx pm2 start api.config.cjs --no-daemon --env TEST +elif [ "$env" == "PRODUCTION" ]; then + echo "PHS API Production Stack" + npm install; + npx babel src --out-dir src --extensions .jsx; + cd /usr/src/app + npx pm2 start api.config.cjs --no-daemon --env PRODUCTION +else + echo "PHS API Dev Stack" + npm install; npm run start-debug +fi diff --git a/src/config/dbConfig.json b/src/config/dbConfig.json new file mode 100644 index 0000000..db102a1 --- /dev/null +++ b/src/config/dbConfig.json @@ -0,0 +1,12 @@ +{ + "user": "postgres", + "host": "localhost", + "port": "5432", + "password": "ab9cfz12", + "database": "postgres", + "migrations-dir": "./src/migrations", + "migrations-table": "pgmigrations", + "schema": "postgres", + "create-schema": true, + "migrations-schema": "phs" +} \ No newline at end of file diff --git a/src/config/default.json b/src/config/default.json new file mode 100644 index 0000000..a7e3ef9 --- /dev/null +++ b/src/config/default.json @@ -0,0 +1,6 @@ +{ + "keys": { + "secret": "SecretText", + "secret2fa": "67d0c783e3c18887bca5c9340917b7d8" + } +} \ No newline at end of file diff --git a/src/config/secret_key_gen.js b/src/config/secret_key_gen.js new file mode 100644 index 0000000..998ed7c --- /dev/null +++ b/src/config/secret_key_gen.js @@ -0,0 +1,11 @@ +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); +const config = require( '../config/default.json' ); + +// Generate (16 bytes, 128 bits) secret key. Copy output to to the proper key in default.json. +const secretKey = crypto.randomBytes(16).toString('hex'); +logger.debug('key: ' + secretKey); + +const payload = { user_id: 682, verified_token: secretKey }; +const token = jwt.sign(payload, config.keys.secret2fa, { algorithm: 'HS256', expiresIn: '100y' }); +logger.debug('jwt token: ' + token); diff --git a/src/controllers/git.controller.js b/src/controllers/git.controller.js new file mode 100644 index 0000000..1937462 --- /dev/null +++ b/src/controllers/git.controller.js @@ -0,0 +1,60 @@ +const fs = require('fs'); +const sshConfig = { + host: 'localhost', + port: 22, + username: 'git', + privateKey: fs.readFileSync('/home/git/.ssh/git-ui'), +}; + +module.exports = { + createRepo: (req, res, next) => { + const { name, type, user } = req.body; + if (!name.match(/^[a-zA-Z0-9_-]+$/)) { + return res.status(400).json({ error: 'Invalid repository name' }); + } + if (type === 'private' && !user) { + return res.status(400).json({ error: 'Username required for private repository' }); + } + + const conn = new Client(); + conn.on('ready', () => { + const commands = type === 'private' + ? [ + `mkdir /opt/git/${name}.git`, + `cd /opt/git/${name}.git`, + `git init --bare`, + `chown -R ${user}:git /opt/git/${name}.git`, + `chmod -R u+rwX,go-rwx /opt/git/${name}.git`, + ] + : [ + `mkdir /opt/git/${name}.git`, + `cd /opt/git/${name}.git`, + `git init --bare --shared=group`, + `chown -R git:git /opt/git/${name}.git`, + `chmod -R g+rwX /opt/git/${name}.git`, + `find /opt/git/${name}.git -type d -exec chmod g+s {} \\;`, + `git config core.sharedRepository group`, + ]; + + conn.exec(`sudo -u git bash -c "${commands.join(' && ')}"`, (err, stream) => { + if (err) { + conn.end(); + return res.status(500).json({ error: 'SSH command failed' }); + } + let output = ''; + stream.on('data', (data) => (output += data)); + stream.stderr.on('data', (data) => (output += data)); + stream.on('close', (code) => { + conn.end(); + if (code === 0) { + res.json({ message: `Repository ${name}.git created successfully` }); + } else { + res.status(500).json({ error: `Command failed: ${output}` }); + } + }); + }); + }).on('error', (err) => { + res.status(500).json({ error: `SSH connection failed: ${err.message}` }); + }).connect(sshConfig); + } +}; \ No newline at end of file diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..e69de29 diff --git a/src/middleware/custom-startup.js b/src/middleware/custom-startup.js new file mode 100644 index 0000000..74aafa9 --- /dev/null +++ b/src/middleware/custom-startup.js @@ -0,0 +1,10 @@ +const phs_env = process.env.PHS_ENV; + +// Run once at startup +async function runOnce() { + logger.debug('Running custom startup code once'); + // Just return when not on PROD + if (phs_env !== 'PRODUCTION') return true +} + +module.exports = runOnce; \ No newline at end of file diff --git a/src/middleware/loggerMiddleWare.js b/src/middleware/loggerMiddleWare.js new file mode 100644 index 0000000..3fc1159 --- /dev/null +++ b/src/middleware/loggerMiddleWare.js @@ -0,0 +1,57 @@ +const log4js = require("log4js"); +log4js.configure({ + pm2: true, + appenders: { + access: { + type: "dateFile", + filename: "logs/access.log", + pattern: "-yyyy-MM-dd", + category: "http" + }, + app: { + type: "file", + filename: "logs/app.log", + maxLogSize: 10485760, + numBackups: 3 + }, + out: { + type: 'stdout' + }, + errorFile: { + type: "file", + filename: "logs/errors.log", + maxLogSize: 10485760 + }, + errors: { + type: "logLevelFilter", + level: "ERROR", + appender: "errorFile" + }, + forms: { + type: "file", + filename: "logs/forms.log", + maxLogSize: 10485760, + numBackups: 3 + } + }, + categories: { + default: { "appenders": [ "app", "errors", 'out' ], "level": "DEBUG" }, + http: { "appenders": [ "access"], "level": "INFO" } + } +}); + +const logger = log4js.getLogger(); + +module.exports = { + default: log4js.getLogger(), + access: log4js.getLogger('access'), + app: log4js.getLogger('app'), + forms: log4js.getLogger('forms'), + http: log4js.getLogger('access'), + express: log4js.connectLogger(logger, { + level: 'debug', + format: (req, res, format) => format( + ':method :url :status :response-time ms' + ) + }), +}; diff --git a/src/middleware/multer.js b/src/middleware/multer.js new file mode 100644 index 0000000..548fcc6 --- /dev/null +++ b/src/middleware/multer.js @@ -0,0 +1,12 @@ +const multer = require( 'multer' ); + +const upload = multer({ + limits: { fieldSize: 1024 * 1024 * 50 } +}); + +const passThrough = multer( { + limits: { fileSize: 1024 * 1024 * 50 }, + storage: multer.memoryStorage() +} ); + +module.exports = { upload, passThrough }; \ No newline at end of file diff --git a/src/middleware/passport.js b/src/middleware/passport.js new file mode 100644 index 0000000..845bb71 --- /dev/null +++ b/src/middleware/passport.js @@ -0,0 +1,68 @@ +const User = require('../models/user.model'); +const JWTStrategy = require('passport-jwt').Strategy, + ExtractJwt = require('passport-jwt').ExtractJwt; + +const config = require('../config/default.json'); + +// Hooks the JWT Strategy. +function hookJWTStrategy(passport) { + logger.debug('hookJWTStrategy'); + let options = {}; + + // options.secretOrKey = process.env['HASH_KEY']; + options.secretOrKey = config.keys.secret; + options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); + options.ignoreExpiration = true; + + passport.use('jwt', new JWTStrategy(options, function (JWTPayload, callback) { + // return callback(null, {id: 1}); + logger.debug(`JWT-JWTPayload: ${JSON.stringify(JWTPayload)}`); + /*return new User().findOne({ email: JWTPayload.email, is_active: true })*/ + // return new User().findOne({ id: 1, is_active: true }) + return phsdb.query(`select * from phs.users where id = $1;`, [1], { plain: true }) + .then(async user => { + logger.debug('passport: ' + user); + if (!user?.id) { + return callback(null, false); + } else if (user?.id) { + // const roles = [...user.roles].map(r => r.name); + // if (roles.some(r => ['Administrator', 'ExecutiveManager', 'HR'].includes(r))) await rvdb.query('select crypto_key from rt2.users where id = $1;', [user.id], { plain: true }).then(cry => user.crypto_key = cry.crypto_key); + return callback(null, user); + } else { + return callback(null, false); + } + }).catch(error => callback(error, false)); + })); + + /*passport.use('jwt-contact', new JWTStrategy(options, function (JWTPayload, callback) { + logger.debug(`JWT-CONTACT-JWTPayload: ${JSON.stringify(JWTPayload)}`); + return db.contact().findOne({ id: JWTPayload.id, email: JWTPayload.email, is_active: true, is_deleted: false }) + .then(async contact => { + // logger.debug('JWT-CONTACT: ' + JSON.stringify(contact)); + if (!contact?.id) { + return callback(null, false); + } else if (contact?.id) { + return callback(null, contact); + } else { + return callback(null, false); + } + }).catch(error => callback(error, false)); + })) + + passport.use('it', new JWTStrategy(options, function (JWTPayload, callback) { + logger.debug(`JWT-IT-JWTPayload: ${JSON.stringify(JWTPayload)}`); + return db.it().user().findOne({ remote_id: JWTPayload.id, company_id: JWTPayload.company_id, is_active: true, is_deleted: false }) + .then(async user => { + if (!user?.id) { + return callback(null, false); + } else if (user?.id) { + return callback(null, user); + } else { + return callback(null, false); + } + }).catch(error => callback(error, false)); + }))*/ + +} + +module.exports = hookJWTStrategy; \ No newline at end of file diff --git a/src/middleware/routeHelpers.js b/src/middleware/routeHelpers.js new file mode 100644 index 0000000..d78f0ab --- /dev/null +++ b/src/middleware/routeHelpers.js @@ -0,0 +1,47 @@ +const validateAuth = function ( passport, context='jwt') { + logger.debug('validateAuth'); + async function checkKey( req, res, next ) { + const { authorization, apikey:apiKey } = req.headers; + if (authorization) return passport.authenticate( context, { session:false } )(req, res, next); + // else if (!authorization && !apiKey) return passport.authenticate( context, { session: false } )(req, res, next); + else { + const key = await phsdb.query( 'select * from phs.api_keys where api_key = $1;', [apiKey], { plain:true } ); + const user = await phsdb.query( 'select * from phs.users where id = 1;', [], { plain:true } ); + + logger.debug('helper: ' + user); + + // const user = key ? await db.user().findOne( { id:key.user_id, is_deleted:false, is_active:true } ) : undefined; + /*const roles = user ? await phsdb.query(` + select r.* + from rt2.api_key_roles ur + inner join rt2.roles r on r.id = ur.role_id + where ur.api_key_id = $1;`, [key.id] ) : undefined;*/ + /*if (user && roles) { + user.roles = roles; + }*/ + if (user) { + req.user = user; + } + next(); + } + } + + return checkKey; +}; + +const allowApi = function (apiKey, callback) { + logger.debug('allowApi'); + function checkKey(req, res, next) { + logger.debug(req.params); + if (apiKey === req.params.apiKey) { + callback(req, res, next); + } else { + res.status(401).send({message: 'Your apiKey is not authorized!'}); + } + } + return checkKey; +}; + +module.exports = { validateAuth, allowApi }; + +/** @namespace params.apiKey */ diff --git a/src/models/model.js b/src/models/model.js new file mode 100644 index 0000000..355741f --- /dev/null +++ b/src/models/model.js @@ -0,0 +1,215 @@ +// noinspection JSUnusedGlobalSymbols +class Model { + constructor( props ) { + props && Object.keys( props ).forEach( c => { + this[c] = props[c]; + } ); + this.table = ''; + this.defaultColumns = []; + this.updateExcludeColumns = ['id']; + this.prepend = ''; + this.baseQuery = ''; + this.baseListQuery = ''; + this.defaultOrderBy = undefined; + this.instance = _props => new Model( _props ); + }; + + whereClause = ( keys, prepend ) => keys?.length > 0 ? `where ${ keys.map( ( k, index ) => `${ prepend }.${ k } = $${ index + 1 }` ).join( ' and ' ) }` : ''; + + /** + * Build where clause for query + * + * @param {*} where + * @param {*} defaultColumns + * @returns + */ + buildWhere = function ( where, defaultColumns ) { + const keys = []; + const values = []; + where && Object.keys( where ).forEach( k => { + if (defaultColumns.includes( k )) { + keys.push( k ); + values.push( where[k] ); + } + } ); + return { keys, values }; + }; + + /** + * Find one record + * + * @param {*} where + * @param {*} [excludes] + * @returns + */ + findOne = function ( where, excludes = [] ) { + const self = this; + const { keys, values } = self.buildWhere( where, self.defaultColumns ); + return phsdb.query( ` + ${ self.baseQuery } + ${ self.whereClause( keys, self.prepend ) } + ${ self.groupBy ? self.groupBy : '' } + `, values, { plain:true } ).then( result => { + const found = self.instance( result ); + excludes?.map( e => delete found[e] ); + return found; + } ); + }; + + /** + * Find one record + * + * @param {*} where + * @param {*} [excludes] + * @returns + */ + findOneSimple = function ( where, excludes = [] ) { + const self = this; + const { keys, values } = self.buildWhere( where, self.defaultColumns ); + return phsdb.query( ` + ${ self.baseQuery } + ${ self.whereClause( keys, self.prepend ) } + ${ self.groupBy ? self.groupBy : '' } + `, values, { plain:true } ).then( result => { + excludes?.map( e => delete result[e] ); + return result?.length > 0 ? result : null; + } ); + }; + + /** + * @typedef {Object} orderBy + * @property {string} name + * @property {('asc', 'desc')} direction + */ + + getBaseListQuery = function () { + const self = this; + return ` + ${ self.baseListQuery } + `; + }; + + /** + * Find many records + * + * @param {Object|undefined} [where] + * @param {string[]|undefined} [excludes] + * @param {orderBy|undefined} [orderBy] This can needs to be passed as an object with a name and optionally a direction. You can look at the vehicle controller and model for a good example. + * @returns + */ + findMany = function ( where, excludes, orderBy ) { + const self = this; + const { keys, values } = self.buildWhere( where, self.defaultColumns ); + return phsdb.query( ` + ${ self.getBaseListQuery( self.whereClause( keys, self.prepend ) ) } + ${ self.whereClause( keys, self.prepend ) } + ${ self.groupBy ? self.groupBy : '' } + ${ orderBy ? `order by ${ orderBy.name } ${ orderBy.direction ?? 'asc' }` : self.defaultOrderBy ?? '' }; + `, values ).then( async results => { + const res = []; + await results.forEach( result => { + const found = self.instance( result ); + excludes?.map( e => delete found[e] ); + return res.push( found ); + } ); + return res; + } ); + }; + + /** + * Find many records + * + * @param {Object|undefined} [where] + * @param {string[]|undefined} [excludes] + * @param {orderBy|undefined} [orderBy] This can needs to be passed as an object with a name and optionally a direction. You can look at the vehicle controller and model for a good example. + * @returns + */ + findManyLite = function ( where, excludes, orderBy ) { + const self = this; + const { keys, values } = self.buildWhere( where, self.defaultColumns ); + return phsdb.query( ` + ${ self.getBaseListQuery( self.whereClause( keys, self.prepend ) ) } + ${ self.whereClause( keys, self.prepend ) } + ${ self.groupBy ? self.groupBy : '' } + ${ orderBy ? `order by ${ orderBy.name } ${ orderBy.direction ?? 'asc' }` : self.defaultOrderBy ?? '' }; + `, values ).then( async results => { + const res = []; + await results.forEach( result => { + const dataValues = {}; + Object.keys( result ).map( k => dataValues[k] = result[k] ); + excludes?.map( e => delete dataValues[e] ); + return res.push( dataValues ); + } ); + return res; + } ); + }; + + /** + * Update the record. Will update only the fields present + * in the params + * + * @param {*} params + * @returns + */ + update = async function ( params ) { + const self = this; + + // build list of update values + const { updates, values, position } = self.createUpdateFields( params ); + + // add id for where query + values.push( this.id ); + + // build query from values + const query = 'update ' + this.table + ' set ' + updates + ' where id=$' + position + ' returning *;'; + logger.debug( `query: ${ query }` ); + + return await phsdb.query( query, values, { plain:true } ) + .then( () => { + return self.findOne( { id:this.id } ); + } ); + }; + + toJSON = function () { + const { + defaultColumns, + prepend, + baseQuery, + baseListQuery, + table, + updateExcludeColumns, + agingDownloadQuery, + baseListExtendedQuery, + baseListQueryForTech, + instance, + db, + defaultOrderBy, + excludes, + ...rest + } = this; + return rest; + }; + + /** + * Create update fields string and values based on + * the params and the column configuration + */ + createUpdateFields( params ) { + let position = 1; + let values = []; + let updates = ''; + for (const column of this.defaultColumns) { + if (params.hasOwnProperty( column ) && !this.updateExcludeColumns.includes( column )) { + const value = params[column]; + this[column] = value; + values.push( value ); + updates += ((updates ? ',' : '') + column + '=$' + position++); + } + } + + return { updates, values, position }; + } + +} + +module.exports = Model; \ No newline at end of file diff --git a/src/phsdb.js b/src/phsdb.js new file mode 100644 index 0000000..58f9c5a --- /dev/null +++ b/src/phsdb.js @@ -0,0 +1,90 @@ +// noinspection RequiredAttributes + +const { Pool } = require('pg'); +const connectionString = 'postgres://postgres:ab9cfz12@clr-db:5432/postgres'; +const pgp = require('pg-promise')(); + +const pool = new Pool({ + connectionString, + max: 50 +}); + +const json_slimify = (key, value) => { + if (key === "data") return '' + if (key === "photo_data") return '' + if (key === "photo_data_tn") return '' + if (typeof value === 'string' && value.length > 100) { + return value.substring(0, 100) + '...'; + } else { + return value; + } +} + +// noinspection JSUnusedLocalSymbols +const log_query = (text, values, options={}) => { + try{ + // logger.debug(`log_query: ${text} -- With values: ${JSON.stringify(values,json_slimify)}`); // Incase you need to see the values and properties. + const formattedQuery = pgp.as.format(text, values); + logger.debug( + `[Executing Query] ${formattedQuery} -- With values: ${JSON.stringify( + values,json_slimify + )}`, + ); + } catch (err) { + logger.error('Error formatting query:', err); + } +} + +module.exports.query = async (text, values, options={}) => { + if (!text) return null + if (!values) values = [] + + log_query(text, values, options) + + const results = await pool.query(text, values) + if (results && results.rows) { + return options?.plain ? results.rows[0] : results.rows; + } else { + return null + } +} + +module.exports.get_pool = () => { + return pool +} + +module.exports.cleanup = async () => { + await pool.end() +} + +module.exports.get_client = async () => { + // noinspection JSCheckFunctionSignatures + return await pool.connect(); +} + +module.exports.client_release = async (client) => { + await client.release() +} + +module.exports.client_query = async (client, text, values, options={}) => { + if (!text) return null + if (!values) values = [] + + log_query(text, values, options) + + const results = await client.query(text, values) + if (results && results.rows) { + return options?.plain ? results.rows[0] : results.rows; + } else { + return null + } +} + +module.exports.get_pool_stats = () => { + return { + total: pool.totalCount, + idle: pool.idleCount, + waiting: pool.waitingCount + } +} + diff --git a/src/routes/git.routes.js b/src/routes/git.routes.js new file mode 100644 index 0000000..4734d9d --- /dev/null +++ b/src/routes/git.routes.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const gitController = require('../controllers/git.controller'); + +module.exports = (passport) => { + router.use( validateAuth( passport ) ); + router.post('/create-repo', gitController.createRepo); + + return router; +}; \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..6da96af --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,16 @@ +const express = require('express'); +const router = express.Router(); + +module.exports.APIRoutes = ( passport ) => { + /* GET home page. */ + router.get('/', function(req, res, next) { + res.render('index', { title: 'Express' }); + }); + + router.use( '/git', require('./git.routes')(passport) ); + return router; +} + + + +module.exports = router; diff --git a/src/routes/users.js b/src/routes/users.js new file mode 100644 index 0000000..623e430 --- /dev/null +++ b/src/routes/users.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +router.get('/', function(req, res, next) { + res.send('respond with a resource'); +}); + +module.exports = router; diff --git a/src/swagger.json b/src/swagger.json new file mode 100644 index 0000000..8c330fb --- /dev/null +++ b/src/swagger.json @@ -0,0 +1,2587 @@ +{ + "swagger": "2.0", + "info": { + "description": "API for managing units, inspection checks, and related entities", + "version": "1.0.0", + "title": "CES API", + "contact": { + "email": "abc@gmail.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "schemes": ["https", "http"], + "host": "api-phasecustomsoft.com", + "basePath": "/", + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, + "security": [{"Bearer": []}], + "paths": { + "/base_inspection_check/": { + "post": { + "tags": ["Base Inspection Check"], + "summary": "Create a new base inspection check", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Base inspection check object", + "required": true, + "schema": { + "$ref": "#/definitions/BaseInspection" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/BaseInspection" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/base_inspection_check/{inspection_check_id}": { + "get": { + "tags": ["Base Inspection Check"], + "summary": "Get base inspection check by ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "inspection_check_id", + "in": "path", + "description": "ID of base inspection check to return", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/BaseInspection" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Base inspection check not found" + } + } + }, + "delete": { + "tags": ["Base Inspection Check"], + "summary": "Delete base inspection check", + "description": "", + "parameters": [ + { + "name": "inspection_check_id", + "in": "path", + "description": "ID of base inspection check to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Base inspection check not found" + } + } + } + }, + "/base_inspection_check/dot": { + "get": { + "tags": ["Base Inspection Check"], + "summary": "Get DOT-related base inspection checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/BaseInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/base_inspection_check/static": { + "get": { + "tags": ["Base Inspection Check"], + "summary": "Get static base inspection checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/BaseInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/base_inspection_check/{inspection_check_id}/photo-requirements": { + "put": { + "tags": ["Base Inspection Check"], + "summary": "Update photo requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "inspection_check_id", + "in": "path", + "description": "ID of base inspection check to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Photo requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "photo": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/BaseInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Base inspection check not found" + } + } + } + }, + "/base_inspection_check/{inspection_check_id}/details-requirements": { + "put": { + "tags": ["Base Inspection Check"], + "summary": "Update details requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "inspection_check_id", + "in": "path", + "description": "ID of base inspection check to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Details requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/BaseInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Base inspection check not found" + } + } + } + }, + "/base_inspection_check/{inspection_check_id}/sort-order": { + "put": { + "tags": ["Base Inspection Check"], + "summary": "Update sort order", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "inspection_check_id", + "in": "path", + "description": "ID of base inspection check to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Sort order object", + "required": true, + "schema": { + "type": "object", + "properties": { + "sort_order": { + "type": "number", + "format": "double" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/BaseInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Base inspection check not found" + } + } + } + }, + "/user/": { + "post": { + "tags": ["User"], + "summary": "Create a new user", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "User object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/user/by-employee-number/{employee_number}": { + "get": { + "tags": ["User"], + "summary": "Get user by employee number", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "employee_number", + "in": "path", + "description": "Employee number of user to return", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid employee number supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/active": { + "get": { + "tags": ["User"], + "summary": "Get active users", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/user/{id}/deactivate": { + "put": { + "tags": ["User"], + "summary": "Deactivate user", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of user to deactivate", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Deactivation details", + "required": true, + "schema": { + "type": "object", + "properties": { + "deactivatedById": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/{id}/reactivate": { + "put": { + "tags": ["User"], + "summary": "Reactivate user", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of user to reactivate", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/{id}": { + "put": { + "tags": ["User"], + "summary": "Update user", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of user to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "User object to update", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": ["User"], + "summary": "Delete user", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of user to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/unit_category/": { + "post": { + "tags": ["Unit Category"], + "summary": "Create a new unit category", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Unit category object", + "required": true, + "schema": { + "$ref": "#/definitions/UnitCategory" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/UnitCategory" + } + }, + "400": { + "description": "Invalid input" + } + } + }, + "get": { + "tags": ["Unit Category"], + "summary": "Get all unit categories", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitCategory" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category/by-name/{name}": { + "get": { + "tags": ["Unit Category"], + "summary": "Get unit category by name", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Name of unit category to return", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategory" + } + }, + "400": { + "description": "Invalid name supplied" + }, + "404": { + "description": "Unit category not found" + } + } + } + }, + "/unit_category/name-exists/{name}": { + "get": { + "tags": ["Unit Category"], + "summary": "Check if unit category name exists", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Name to check", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "object", + "properties": { + "exists": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category/{id}/name": { + "put": { + "tags": ["Unit Category"], + "summary": "Update unit category name", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of unit category to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "New name object", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategory" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit category not found" + } + } + } + }, + "/unit_category/{id}": { + "delete": { + "tags": ["Unit Category"], + "summary": "Delete unit category", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of unit category to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Unit category not found" + } + } + } + }, + "/unit_type_inspection_check/": { + "post": { + "tags": ["Unit Type Inspection Check"], + "summary": "Create a new unit type inspection check", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Unit type inspection check object", + "required": true, + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_type_inspection_check/{unit_type_id}/{inspection_check_id}": { + "get": { + "tags": ["Unit Type Inspection Check"], + "summary": "Get unit type inspection check by IDs", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + }, + "400": { + "description": "Invalid IDs supplied" + }, + "404": { + "description": "Unit type inspection check not found" + } + } + } + }, + "/unit_type_inspection_check/by-unit-type/{unit_type_id}": { + "get": { + "tags": ["Unit Type Inspection Check"], + "summary": "Get checks by unit type", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitTypeInspection" + } + } + }, + "400": { + "description": "Invalid unit_type_id supplied" + } + } + } + }, + "/unit_type_inspection_check/dot": { + "get": { + "tags": ["Unit Type Inspection Check"], + "summary": "Get DOT-related checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitTypeInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_type_inspection_check/static": { + "get": { + "tags": ["Unit Type Inspection Check"], + "summary": "Get static checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitTypeInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_type_inspection_check/{unit_type_id}/{inspection_check_id}/photo-requirements": { + "put": { + "tags": ["Unit Type Inspection Check"], + "summary": "Update photo requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Photo requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "photo": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit type inspection check not found" + } + } + } + }, + "/unit_type_inspection_check/{unit_type_id}/{inspection_check_id}/details-requirements": { + "put": { + "tags": ["Unit Type Inspection Check"], + "summary": "Update details requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Details requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit type inspection check not found" + } + } + } + }, + "/unit_type_inspection_check/{unit_type_id}/{inspection_check_id}/sort-order": { + "put": { + "tags": ["Unit Type Inspection Check"], + "summary": "Update sort order", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Sort order object", + "required": true, + "schema": { + "type": "object", + "properties": { + "sort_order": { + "type": "number", + "format": "double" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitTypeInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit type inspection check not found" + } + } + } + }, + "/api_key/": { + "post": { + "tags": ["API Key"], + "summary": "Create a new API key", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "API key object", + "required": true, + "schema": { + "$ref": "#/definitions/ApiKey" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/ApiKey" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/api_key/{id}": { + "get": { + "tags": ["API Key"], + "summary": "Get API key by ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of API key to return", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/ApiKey" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "API key not found" + } + } + }, + "put": { + "tags": ["API Key"], + "summary": "Update API key", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of API key to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "API key object to update", + "required": true, + "schema": { + "$ref": "#/definitions/ApiKey" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/ApiKey" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "API key not found" + } + } + }, + "delete": { + "tags": ["API Key"], + "summary": "Delete API key", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of API key to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "API key not found" + } + } + } + }, + "/api_key/by-key/{apiKey}": { + "get": { + "tags": ["API Key"], + "summary": "Get API key by value", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "apiKey", + "in": "path", + "description": "API key value to return", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/ApiKey" + } + }, + "400": { + "description": "Invalid API key supplied" + }, + "404": { + "description": "API key not found" + } + } + } + }, + "/unit/": { + "get": { + "tags": ["Unit"], + "summary": "Get all units", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Unit" + } + } + }, + "400": { + "description": "Invalid input" + } + } + }, + "post": { + "tags": ["Unit"], + "summary": "Create a new unit", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Unit object", + "required": true, + "schema": { + "$ref": "#/definitions/Unit" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/Unit" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit/by-unit-type/{unit_type_id}": { + "get": { + "tags": ["Unit"], + "summary": "Get units by unit type ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_type_id", + "in": "path", + "description": "Unit type ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Unit" + } + } + }, + "400": { + "description": "Invalid unit_type_id supplied" + } + } + } + }, + "/unit/by-unit-category/{unit_category_id}": { + "get": { + "tags": ["Unit"], + "summary": "Get units by unit category ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Unit" + } + } + }, + "400": { + "description": "Invalid unit_category_id supplied" + } + } + } + }, + "/unit/inspections/": { + "get": { + "tags": ["Unit"], + "summary": "Get inspections by lookup code", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Lookup code", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/InspectionCheck" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category_inspection_check/": { + "post": { + "tags": ["Unit Category Inspection Check"], + "summary": "Create a new unit category inspection check", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Unit category inspection check object", + "required": true, + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category_inspection_check/{unit_category_id}/{inspection_check_id}": { + "get": { + "tags": ["Unit Category Inspection Check"], + "summary": "Get unit category inspection check by IDs", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + }, + "400": { + "description": "Invalid IDs supplied" + }, + "404": { + "description": "Unit category inspection check not found" + } + } + } + }, + "/unit_category_inspection_check/by-unit-category/{unit_category_id}": { + "get": { + "tags": ["Unit Category Inspection Check"], + "summary": "Get checks by unit category", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitCategoryInspection" + } + } + }, + "400": { + "description": "Invalid unit_category_id supplied" + } + } + } + }, + "/unit_category_inspection_check/dot": { + "get": { + "tags": ["Unit Category Inspection Check"], + "summary": "Get DOT-related checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitCategoryInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category_inspection_check/static": { + "get": { + "tags": ["Unit Category Inspection Check"], + "summary": "Get static checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UnitCategoryInspection" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/unit_category_inspection_check/{unit_category_id}/{inspection_check_id}/photo-requirements": { + "put": { + "tags": ["Unit Category Inspection Check"], + "summary": "Update photo requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Photo requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "photo": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit category inspection check not found" + } + } + } + }, + "/unit_category_inspection_check/{unit_category_id}/{inspection_check_id}/details-requirements": { + "put": { + "tags": ["Unit Category Inspection Check"], + "summary": "Update details requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Details requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit category inspection check not found" + } + } + } + }, + "/unit_category_inspection_check/{unit_category_id}/{inspection_check_id}/sort-order": { + "put": { + "tags": ["Unit Category Inspection Check"], + "summary": "Update sort order", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_category_id", + "in": "path", + "description": "Unit category ID", + "required": true, + "type": "integer" + }, + { + "name": "inspection_check_id", + "in": "path", + "description": "Inspection check ID", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Sort order object", + "required": true, + "schema": { + "type": "object", + "properties": { + "sort_order": { + "type": "number", + "format": "double" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/UnitCategoryInspection" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Unit category inspection check not found" + } + } + } + }, + "/inspection_check/": { + "post": { + "tags": ["Inspection Check"], + "summary": "Create a new inspection check", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Inspection check object", + "required": true, + "schema": { + "$ref": "#/definitions/InspectionCheck" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/InspectionCheck" + } + }, + "400": { + "description": "Invalid input" + } + } + }, + "get": { + "tags": ["Inspection Check"], + "summary": "Get all inspection checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/InspectionCheck" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/inspection_check/by-name/{name}": { + "get": { + "tags": ["Inspection Check"], + "summary": "Get inspection check by name", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Name of inspection check to return", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/InspectionCheck" + } + }, + "400": { + "description": "Invalid name supplied" + }, + "404": { + "description": "Inspection check not found" + } + } + } + }, + "/inspection_check/dot": { + "get": { + "tags": ["Inspection Check"], + "summary": "Get DOT-related inspection checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/InspectionCheck" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/inspection_check/static": { + "get": { + "tags": ["Inspection Check"], + "summary": "Get static inspection checks", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/InspectionCheck" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/inspection_check/{id}/photo-requirements": { + "put": { + "tags": ["Inspection Check"], + "summary": "Update photo requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of inspection check to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Photo requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "photo": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/InspectionCheck" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Inspection check not found" + } + } + } + }, + "/inspection_check/{id}/details-requirements": { + "put": { + "tags": ["Inspection Check"], + "summary": "Update details requirements", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of inspection check to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Details requirements object", + "required": true, + "schema": { + "type": "object", + "properties": { + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/InspectionCheck" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Inspection check not found" + } + } + } + }, + "/inspection_check/{id}": { + "delete": { + "tags": ["Inspection Check"], + "summary": "Delete inspection check", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of inspection check to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Inspection check not found" + } + } + } + }, + "/form/": { + "post": { + "tags": ["Form"], + "summary": "Create a new form", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Form object", + "required": true, + "schema": { + "$ref": "#/definitions/Form" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/Form" + } + }, + "400": { + "description": "Invalid input" + } + } + }, + "get": { + "tags": ["Form"], + "summary": "Get all forms", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Form" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/form/{id}": { + "get": { + "tags": ["Form"], + "summary": "Get form by ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form to return", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/Form" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Form not found" + } + } + }, + "put": { + "tags": ["Form"], + "summary": "Update form", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Form object to update", + "required": true, + "schema": { + "$ref": "#/definitions/Form" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/Form" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Form not found" + } + } + }, + "delete": { + "tags": ["Form"], + "summary": "Delete form", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Form not found" + } + } + } + }, + "/form/by-unit/{unit_id}": { + "get": { + "tags": ["Form"], + "summary": "Get forms by unit ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "unit_id", + "in": "path", + "description": "Unit ID to filter forms", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Form" + } + } + }, + "400": { + "description": "Invalid unit_id supplied" + } + } + } + }, + "/form/by-user/{user_id}": { + "get": { + "tags": ["Form"], + "summary": "Get forms by user ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "user_id", + "in": "path", + "description": "User ID to filter forms", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Form" + } + } + }, + "400": { + "description": "Invalid user_id supplied" + } + } + } + }, + "/form_attachment/": { + "post": { + "tags": ["Form Attachment"], + "summary": "Create a new form attachment", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Form attachment object", + "required": true, + "schema": { + "$ref": "#/definitions/FormAttachment" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/FormAttachment" + } + }, + "400": { + "description": "Invalid input" + } + } + }, + "get": { + "tags": ["Form Attachment"], + "summary": "Get all form attachments", + "description": "", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FormAttachment" + } + } + }, + "400": { + "description": "Invalid input" + } + } + } + }, + "/form_attachment/{id}": { + "get": { + "tags": ["Form Attachment"], + "summary": "Get form attachment by ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form attachment to return", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/FormAttachment" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Form attachment not found" + } + } + }, + "put": { + "tags": ["Form Attachment"], + "summary": "Update form attachment", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form attachment to update", + "required": true, + "type": "integer" + }, + { + "in": "body", + "name": "body", + "description": "Form attachment object to update", + "required": true, + "schema": { + "$ref": "#/definitions/FormAttachment" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "$ref": "#/definitions/FormAttachment" + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Form attachment not found" + } + } + }, + "delete": { + "tags": ["Form Attachment"], + "summary": "Delete form attachment", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of form attachment to delete", + "required": true, + "type": "integer" + } + ], + "responses": { + "204": { + "description": "Deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Form attachment not found" + } + } + } + }, + "/form_attachment/by-form/{form_id}": { + "get": { + "tags": ["Form Attachment"], + "summary": "Get form attachments by form ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "form_id", + "in": "path", + "description": "Form ID to filter attachments", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FormAttachment" + } + } + }, + "400": { + "description": "Invalid form_id supplied" + } + } + } + }, + "/form_attachment/by-creator/{created_by_id}": { + "get": { + "tags": ["Form Attachment"], + "summary": "Get form attachments by creator ID", + "description": "", + "produces": ["application/json"], + "parameters": [ + { + "name": "created_by_id", + "in": "path", + "description": "User ID who created the attachments", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FormAttachment" + } + } + }, + "400": { + "description": "Invalid created_by_id supplied" + } + } + } + } + }, + "definitions": { + "BaseInspection": { + "type": "object", + "properties": { + "inspection_check_id": { + "type": "integer" + }, + "sort_order": { + "type": "number", + "format": "double" + }, + "dot": { + "type": "boolean" + }, + "photo": { + "type": "boolean" + }, + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + }, + "is_static": { + "type": "boolean" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "employee_number": { + "type": "string" + }, + "password": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "deactivated_at": { + "type": "string", + "format": "date-time" + }, + "deactivated_by_id": { + "type": "integer" + } + } + }, + "UnitCategory": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "UnitTypeInspection": { + "type": "object", + "properties": { + "unit_type_id": { + "type": "integer" + }, + "inspection_check_id": { + "type": "integer" + }, + "sort_order": { + "type": "number", + "format": "double" + }, + "dot": { + "type": "boolean" + }, + "photo": { + "type": "boolean" + }, + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + }, + "is_static": { + "type": "boolean" + } + } + }, + "Unit": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "lookup_code": { + "type": "string" + }, + "unit_id": { + "type": "string" + }, + "unit_category_id": { + "type": "integer" + }, + "unit_type_id": { + "type": "integer" + } + } + }, + "UnitCategoryInspection": { + "type": "object", + "properties": { + "unit_category_id": { + "type": "integer" + }, + "inspection_check_id": { + "type": "integer" + }, + "sort_order": { + "type": "number", + "format": "double" + }, + "dot": { + "type": "boolean" + }, + "photo": { + "type": "boolean" + }, + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + }, + "is_static": { + "type": "boolean" + } + } + }, + "ApiKey": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "api_key": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "InspectionCheck": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "dot": { + "type": "boolean" + }, + "photo": { + "type": "boolean" + }, + "details": { + "type": "boolean" + }, + "details_required": { + "type": "boolean" + }, + "photo_required": { + "type": "boolean" + }, + "photo_only": { + "type": "boolean" + }, + "photo_count": { + "type": "integer" + }, + "is_static": { + "type": "boolean" + } + } + }, + "UnitType": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "unit_category_id": { + "type": "integer" + } + } + }, + "Form": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "unit_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "form_data": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "FormAttachment": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "form_id": { + "type": "integer" + }, + "file_data": { + "type": "string", + "format": "byte" + }, + "file_type": { + "type": "string" + }, + "created_by_id": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + } +} \ No newline at end of file