Initial commit
This commit is contained in:
33
src/handlers/config.ts
Normal file
33
src/handlers/config.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import ConfigManager from '../libs/configManager.js';
|
||||
import __path from "./path.js";
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
// Create a new config instance.
|
||||
const config = new ConfigManager(__path + '/config.json', true, {
|
||||
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||
http_listen_address: '0.0.0.0',
|
||||
http_port: 3000,
|
||||
debug: true,
|
||||
auth: {
|
||||
cookie_secret: 'gen',
|
||||
cookie_secure: true,
|
||||
local: {
|
||||
active: true,
|
||||
users: {}
|
||||
},
|
||||
oidc: {
|
||||
active: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no local User exists, create the default with a generated password
|
||||
if (_.isEqual(config.global.auth.local.users, {})) {
|
||||
config.global.auth.local.users = {
|
||||
'administrator': 'gen',
|
||||
};
|
||||
config.save_config();
|
||||
}
|
||||
|
||||
export default config;
|
12
src/handlers/db.ts
Normal file
12
src/handlers/db.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { PrismaClient } from '@prisma/client'; // Database
|
||||
import config from "./config.js";
|
||||
|
||||
// TODO: Add errorhandling with some sort of message.
|
||||
export const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
url: config.global.db_connection_string
|
||||
}
|
||||
}
|
||||
});
|
||||
|
34
src/handlers/log.ts
Normal file
34
src/handlers/log.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Logger } from "tslog";
|
||||
|
||||
const loggerConfig: any = {
|
||||
type: "pretty", // pretty, json, hidden
|
||||
name: "Core",
|
||||
hideLogPositionForProduction: true,
|
||||
prettyLogTemplate: "{{dateIsoStr}} {{logLevelName}} {{nameWithDelimiterPrefix}} "
|
||||
}
|
||||
|
||||
const coreLogger = new Logger(loggerConfig);
|
||||
export const log = {
|
||||
core: coreLogger,
|
||||
db: coreLogger.getSubLogger({ name: "DB" }),
|
||||
web: coreLogger.getSubLogger({ name: "WEB" }),
|
||||
auth: coreLogger.getSubLogger({ name: "AUTH" }),
|
||||
helper: coreLogger.getSubLogger({ name: "HELPER" }),
|
||||
};
|
||||
|
||||
|
||||
// log.core.silly("Hello from core");
|
||||
// log.core.trace("Hello from core");
|
||||
// log.core.debug("Hello from core");
|
||||
// log.core.info("Hello from core");
|
||||
// log.core.warn("Hello from core");
|
||||
// log.core.error("Hello from core");
|
||||
// log.db.silly("Hello from db");
|
||||
// log.db.trace("Hello from db");
|
||||
// log.web.debug("Hello from db");
|
||||
// log.auth.info("Hello from db");
|
||||
// log.helper.warn("Hello from db");
|
||||
// log.db.error("Hello from db");
|
||||
// log.core.fatal(new Error("I am a pretty Error with a stacktrace."));
|
||||
|
||||
export default log;
|
4
src/handlers/path.ts
Normal file
4
src/handlers/path.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// Return the app directory as an absolute path
|
||||
const __path = process.argv[1];
|
||||
|
||||
export default __path;
|
84
src/index.ts
Normal file
84
src/index.ts
Normal file
@ -0,0 +1,84 @@
|
||||
// MARK: Imports
|
||||
import path from 'node:path';
|
||||
import __path from "./handlers/path.js";
|
||||
import log from "./handlers/log.js";
|
||||
import config from './handlers/config.js';
|
||||
|
||||
// Express & more
|
||||
import express from 'express';
|
||||
import cors from 'cors'
|
||||
import session from 'express-session';
|
||||
import fileUpload from 'express-fileupload';
|
||||
import bodyParser, { Options } from 'body-parser';
|
||||
import { Eta } from "eta";
|
||||
import passport from 'passport';
|
||||
|
||||
import routes from './routes/index.js';
|
||||
|
||||
log.core.trace("Running from path: " + __path);
|
||||
|
||||
|
||||
// MARK: Express
|
||||
const app = express();
|
||||
|
||||
// TODO: Version check need to be rewritten.
|
||||
//app.locals.versionRevLong = require('child_process').execSync('git rev-parse HEAD').toString().trim();
|
||||
//app.locals.versionRev = require('child_process').execSync('git rev-parse --short HEAD').toString().trim();
|
||||
//app.locals.versionRevLatest = require('child_process').execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
|
||||
app.locals.versionRev = '0';
|
||||
app.locals.versionRevLong = '0';
|
||||
app.locals.versionRevLatest = '0';
|
||||
|
||||
if (app.locals.versionRevLong === app.locals.versionRevLatest) {
|
||||
log.core.info(`Running Latest Version (${app.locals.versionRevLong})`);
|
||||
} else {
|
||||
log.core.info(`Running Version: ${app.locals.versionRevLong} (Latest: ${app.locals.versionRevLatest})`);
|
||||
}
|
||||
|
||||
// ETA Init
|
||||
const eta = new Eta({ views: path.join(__path, "views") })
|
||||
app.engine("eta", buildEtaEngine())
|
||||
app.set("view engine", "eta")
|
||||
|
||||
|
||||
// MARK: Express Middleware & Config
|
||||
app.set('x-powered-by', false);
|
||||
|
||||
app.use(fileUpload());
|
||||
//app.use(cors());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Session store
|
||||
// TODO: Move secret to config -> Autogenerate.
|
||||
app.use(
|
||||
session({
|
||||
secret: config.global.auth.cookie_secret,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: config.global.auth.cookie_secure }
|
||||
})
|
||||
);
|
||||
app.use(passport.authenticate('session'));
|
||||
app.use(routes);
|
||||
|
||||
|
||||
app.listen(config.global.http_port, config.global.http_listen_address, () => {
|
||||
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
|
||||
});
|
||||
|
||||
|
||||
|
||||
// MARK: Helper Functions
|
||||
function buildEtaEngine() {
|
||||
return (path:string, opts:Options, callback: CallableFunction) => {
|
||||
try {
|
||||
const fileContent = eta.readFile(path);
|
||||
const renderedTemplate = eta.renderString(fileContent, opts);
|
||||
callback(null, renderedTemplate);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
140
src/libs/configManager.ts
Normal file
140
src/libs/configManager.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import fs from 'node:fs';
|
||||
import _ from 'lodash';
|
||||
import { randomUUID, randomBytes } from 'crypto';
|
||||
|
||||
export type configObject = Record<any, any>;
|
||||
|
||||
/**
|
||||
* This class is responsible to save/edit config files.
|
||||
*
|
||||
* @export
|
||||
* @class config
|
||||
* @typedef {config}
|
||||
*/
|
||||
export default class config {
|
||||
#configPath: string;
|
||||
//global = {[key: string] : string}
|
||||
global: configObject;
|
||||
replaceSecrets: boolean;
|
||||
|
||||
/**
|
||||
* Creates an instance of config.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} configPath Path to config file.
|
||||
* @param {object} configPreset Default config object with default values.
|
||||
*/
|
||||
constructor(configPath: string, replaceSecrets: boolean, configPreset: object) {
|
||||
this.#configPath = configPath;
|
||||
this.global = configPreset;
|
||||
this.replaceSecrets = replaceSecrets;
|
||||
|
||||
try {
|
||||
// Read config
|
||||
const data = fs.readFileSync(this.#configPath, 'utf8');
|
||||
|
||||
// Extend config with missing parameters from configPreset.
|
||||
this.global = _.defaultsDeep(JSON.parse(data), this.global);
|
||||
// Save config.
|
||||
this.save_config();
|
||||
} catch (err: any) {
|
||||
// If file does not exist, create it.
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log(`Config file does not exist. Creating it at ${this.#configPath} now.`);
|
||||
this.save_config();
|
||||
return;
|
||||
}
|
||||
console.error(`Could not read config file at ${this.#configPath} due to: ${err}`);
|
||||
// Exit process.
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the jsonified config object to the config file.
|
||||
*/
|
||||
save_config() {
|
||||
try {
|
||||
// If enabled replace tokens defines as "gen" with random token
|
||||
if (this.replaceSecrets) {
|
||||
// Replace tokens with value "gen"
|
||||
this.generate_secrets(this.global, 'gen')
|
||||
}
|
||||
|
||||
fs.writeFileSync(this.#configPath, JSON.stringify(this.global, null, 8));
|
||||
} catch (err) {
|
||||
console.error(`Could not write config file at ${this.#configPath} due to: ${err}`);
|
||||
return;
|
||||
}
|
||||
console.log(`Successfully written config file to ${this.#configPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces each item matching the value of placeholder with a random UUID.
|
||||
* Thanks to https://stackoverflow.com/questions/8085004/iterate-through-nested-javascript-objects
|
||||
* @param {configObject} obj
|
||||
*/
|
||||
generate_secrets(obj: configObject, placeholder: string) {
|
||||
const stack = [obj];
|
||||
while (stack?.length > 0) {
|
||||
const currentObj:any = stack.pop();
|
||||
Object.keys(currentObj).forEach((key) => {
|
||||
|
||||
if (currentObj[key] === placeholder) {
|
||||
console.log('Generating secret: ' + key);
|
||||
currentObj[key] = randomBytes(48).toString('base64').replace(/\W/g, '');
|
||||
}
|
||||
|
||||
if (typeof currentObj[key] === 'object' && currentObj[key] !== null) {
|
||||
stack.push(currentObj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
**** Example ****
|
||||
|
||||
import ConfigHandlerNG from './assets/configHandlerNG.js';
|
||||
|
||||
// Create a new config instance.
|
||||
export const config = new ConfigHandler(__path + '/config.json', true, {
|
||||
test1: 't1',
|
||||
test2: 't2',
|
||||
test3: 'gen',
|
||||
test4: 't4',
|
||||
test5: 'gen',
|
||||
testObj: {
|
||||
local: {
|
||||
active: true,
|
||||
users: {
|
||||
user1: 'gen',
|
||||
user2: 'gen',
|
||||
user3: 'gen',
|
||||
user4: 'gen',
|
||||
|
||||
}
|
||||
},
|
||||
oidc: {
|
||||
active: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Base Config:');
|
||||
console.log(config.global);
|
||||
|
||||
console.log('Add some new key to config and call save_config().');
|
||||
config.global.NewKey = 'ThisIsANewKey!'
|
||||
config.save_config()
|
||||
|
||||
console.log('This will add a new key with value gen, but gen gets replaced with a random UUID when save_config() is called.');
|
||||
config.global.someSecret = 'gen'
|
||||
config.save_config() // global.someSecret is getting replaced with some random UUID since it was set to 'gen'.
|
||||
|
||||
console.log('Complete Config:');
|
||||
console.log(config.global);
|
||||
*/
|
25
src/middlewares/auth.mw.ts
Normal file
25
src/middlewares/auth.mw.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export function checkAuthentication(req: any, res: any, next: Function) {
|
||||
|
||||
next(); // FIXME: Auth bypass!!!
|
||||
return; // FIXME: Auth bypass!!!
|
||||
|
||||
if (req.isAuthenticated()) {
|
||||
//req.isAuthenticated() will return true if user is logged in
|
||||
next();
|
||||
} else {
|
||||
res.redirect('/auth/login');
|
||||
}
|
||||
}
|
||||
|
||||
// const checkIsInRole = (...roles) => (req, res, next) => {
|
||||
// if (!req.user) {
|
||||
// return res.redirect('/login')
|
||||
// }
|
||||
|
||||
// const hasRole = roles.find(role => req.user.role === role)
|
||||
// if (!hasRole) {
|
||||
// return res.redirect('/login')
|
||||
// }
|
||||
|
||||
// return next()
|
||||
// }
|
11
src/routes/api/index.ts
Normal file
11
src/routes/api/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import express from 'express';
|
||||
|
||||
// Route imports
|
||||
import v1_routes from './v1/index.js';
|
||||
|
||||
// Router base is '/api'
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
Router.use('/v1', v1_routes);
|
||||
|
||||
export default Router;
|
40
src/routes/api/v1/index.ts
Normal file
40
src/routes/api/v1/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
|
||||
// Route imports
|
||||
import testRoute from './test.js';
|
||||
//import itemRoute from './items.js';
|
||||
//import categoryRoute from './categories.js';
|
||||
//import storageUnitRoute from './storageUnits.js';
|
||||
//import storageLocationRoute from './storageLocations.js';
|
||||
//import contactInfo from './contactInfo.js';
|
||||
//import versionRoute from './version.js'
|
||||
|
||||
//import search_routes from './search/index.js';
|
||||
|
||||
// Router base is '/api/v1'
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
// All empty strings are null values.
|
||||
Router.use('*', function (req, res, next) {
|
||||
for (let key in req.body) {
|
||||
if (req.body[key] === '') {
|
||||
req.body[key] = null;
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
//Router.route('/items').get(itemRoute.get).post(itemRoute.post).patch(itemRoute.patch).delete(itemRoute.del);
|
||||
//Router.route('/categories').get(categoryRoute.get).post(categoryRoute.post).patch(categoryRoute.patch).delete(categoryRoute.del);
|
||||
// TODO: Migrate routes to lowercase.
|
||||
//Router.route('/storageUnits').get(storageUnitRoute.get).post(storageUnitRoute.post).patch(storageUnitRoute.patch).delete(storageUnitRoute.del);
|
||||
//Router.route('/storageLocations').get(storageLocationRoute.get).post(storageLocationRoute.post).patch(storageLocationRoute.patch).delete(storageLocationRoute.del);
|
||||
//Router.route('/contactInfo').get(contactInfo.get).post(contactInfo.post).patch(contactInfo.patch).delete(contactInfo.del);
|
||||
|
||||
//Router.route('/version').get(versionRoute.get);
|
||||
//Router.use('/search', search_routes);
|
||||
|
||||
Router.route('/test').get(testRoute.get);
|
||||
|
||||
export default Router;
|
7
src/routes/api/v1/test.ts
Normal file
7
src/routes/api/v1/test.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
function get(req: Request, res: Response) {
|
||||
res.status(200).send('API v1 Test successful!');
|
||||
};
|
||||
|
||||
export default { get };
|
7
src/routes/frontend/dashboard.ts
Normal file
7
src/routes/frontend/dashboard.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
function get(req: Request, res: Response) {
|
||||
res.render("index", { message: "Hello world from eta!" })
|
||||
}
|
||||
|
||||
export default { get };
|
24
src/routes/frontend/index.ts
Normal file
24
src/routes/frontend/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import express from 'express';
|
||||
|
||||
// Route imports
|
||||
// import skuRoute from './:id.js';
|
||||
// import skuRouteDash from './itemInfo.js'
|
||||
// import testRoute from './test.js';
|
||||
import dashboardRoute from './dashboard.js';
|
||||
// import itemsRoute from './items.js';
|
||||
// import manage_routes from './manage/index.js';
|
||||
|
||||
// Router base is '/'
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
// Router.route('/test').get(testRoute.get);
|
||||
// Router.route('/items').get(itemsRoute.get);
|
||||
|
||||
// Router.route('/:id(\\w{8})').get(skuRoute.get);
|
||||
// Router.route('/s/:id').get(skuRouteDash.get);
|
||||
|
||||
// Router.use('/manage', manage_routes);
|
||||
|
||||
Router.route('/').get(dashboardRoute.get);
|
||||
|
||||
export default Router;
|
37
src/routes/index.ts
Normal file
37
src/routes/index.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import express from 'express';
|
||||
import path from 'node:path';
|
||||
import __path from "../handlers/path.js";
|
||||
import log from "../handlers/log.js";
|
||||
|
||||
// Middleware Imports
|
||||
import { checkAuthentication } from '../middlewares/auth.mw.js'
|
||||
|
||||
// Route imports
|
||||
import frontend_routes from './frontend/index.js';
|
||||
import api_routes from './api/index.js';
|
||||
//import auth_routes from './auth/index.js';
|
||||
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
// static / libs routes
|
||||
Router.use('/static', express.static(__path + '/static'));
|
||||
Router.use('/libs/bulma', express.static(path.join(__path, 'node_modules', 'bulma', 'css'))); // http://192.168.221.10:3000/libs/bulma/bulma.css
|
||||
Router.use('/libs/jquery', express.static(path.join(__path, 'node_modules', 'jquery', 'dist')));
|
||||
|
||||
// Other routers
|
||||
Router.use('/api', checkAuthentication, api_routes);
|
||||
//Router.use('/auth', auth_routes);
|
||||
Router.use('/', checkAuthentication, frontend_routes);
|
||||
|
||||
|
||||
// Default route.
|
||||
Router.all('*', function (req, res) {
|
||||
// TODO: Respond based on content-type (with req.is('application/json'))
|
||||
if (req.is('application/json')) {
|
||||
res.status(404).json({ errorcode: 'NOT_FOUND', error: 'Not Found!' });
|
||||
} else {
|
||||
res.status(404).render('errors/404', { url: req.originalUrl });
|
||||
}
|
||||
});
|
||||
|
||||
export default Router;
|
Reference in New Issue
Block a user