Current state

This commit is contained in:
2025-01-17 23:02:39 +01:00
parent fa06c402e6
commit 534e240eff
24 changed files with 18717 additions and 6974 deletions

View File

@ -1,8 +1,10 @@
import { PrismaClient } from '@prisma/client'; // Database
import config from "./config.js";
import { PrismaClient, Prisma } from '@prisma/client'; // Database
import { Response } from 'express';
import config from './config.js';
import log from './log.js';
// TODO: Add errorhandling with some sort of message.
export const prisma = new PrismaClient({
const prisma = new PrismaClient({
datasources: {
db: {
url: config.global.db_connection_string
@ -10,3 +12,75 @@ export const prisma = new PrismaClient({
}
});
// FIXME: any
export function handlePrismaError(errorObj: any, res: Response) {
log.core.debug(errorObj);
res.status(500).json({ status: 'ERROR', meta: errorObj.meta, errorcode: errorObj.code, message: errorObj.message });
// if(errorObj instanceof Prisma.PrismaClientKnownRequestError)
// switch (errorObj.code) {
// // P2002 -> "Unique constraint failed on the {constraint}"
// // https://www.prisma.io/docs/reference/api-reference/error-reference
// case 'P2002': //
// log.db.error('');
// break;
// // P2003 -> "Foreign key constraint failed on the field: {field_name}"
// // https://www.prisma.io/docs/reference/api-reference/error-reference
// // FIXME: Is this errormessage right?
// case 'P2003': //
// log.db.error('');
// break;
// case 'xxx': //
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// case 'xxx':
// log.db.error('');
// break;
// default:
// break;
// }
// // Check if an entry already exists.
// if (errorcode === 'P2002') {
// // P2002 -> "Unique constraint failed on the {constraint}"
// // https://www.prisma.io/docs/reference/api-reference/error-reference
// res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'Item already exists' });
// } else if (errorcode == 'P2003') {
// // P2003 -> "Foreign key constraint failed on the field: {field_name}"
// // https://www.prisma.io/docs/reference/api-reference/error-reference
// // FIXME: Is this errormessage right?
// res.status(404).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Item does not exist' });
// } else if (errorcode == 'P2000') {
// // P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
// // https://www.prisma.io/docs/reference/api-reference/error-reference
// res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
// } else {
// log.db.error(err);
// res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
// }
}
export default prisma;

View File

@ -1,24 +1,31 @@
import { Logger } from "tslog";
import { Logger,ISettingsParam } from "tslog";
const loggerConfig: any = {
type: "pretty", // pretty, json, hidden
name: "Core",
hideLogPositionForProduction: true,
prettyLogTemplate: "{{dateIsoStr}} {{logLevelName}} {{nameWithDelimiterPrefix}} "
function loggerConfig(name: string): ISettingsParam<unknown> {
return {
type: "pretty", // pretty, json, hidden
name: name,
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" }),
// FIXME: any type
let log: any = {
core: new Logger(loggerConfig("Core")),
db: new Logger(loggerConfig("DB")),
web: new Logger(loggerConfig("Web")),
auth: new Logger(loggerConfig("Auth")),
// helper: new Logger(loggerConfig("HELPER")),
};
log["api"] = log.web.getSubLogger({ name: "API" });
log["frontend"] = log.web.getSubLogger({ name: "Frontend" });
// log.core.silly("Hello from core");
// log.core.trace("Hello from core");
//log.api.trace("Hello from api");
//log.frontend.trace("Hello from frontend");
// log.core.debug("Hello from core");
// log.core.info("Hello from core");
// log.core.warn("Hello from core");

View File

@ -2,6 +2,7 @@
import path from 'node:path';
import __path from "./handlers/path.js";
import log from "./handlers/log.js";
import db from "./handlers/db.js";
import config from './handlers/config.js';
// Express & more
@ -13,28 +14,56 @@ import bodyParser, { Options } from 'body-parser';
import { Eta } from "eta";
import passport from 'passport';
import ChildProcess from 'child_process';
import routes from './routes/index.js';
import fs from 'node:fs';
log.core.trace("Running from path: " + __path);
db.$disconnect();
// 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})`);
// Versioning
try {
const rawPkg = fs.readFileSync("package.json", 'utf8');
const pkgJson = JSON.parse(rawPkg);
app.locals.version = pkgJson.version;
} catch (error) {
log.core.error("Failed to get version from package.json.");
app.locals.version = "0.0.0";
}
try {
app.locals.versionRevLong = ChildProcess.execSync('git rev-parse HEAD').toString().trim();
app.locals.versionRev = app.locals.versionRevLong.substring(0, 7);
} catch (error) {
log.core.error("Failed to get git revision hash.");
app.locals.versionRev = '0';
app.locals.versionRevLong = '0';
}
try {
app.locals.versionRevLatest = ChildProcess.execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
} catch (error) {
log.core.error("Failed to get latest git revision hash.");
app.locals.versionRevLatest = '0';
}
app.locals.versionUpdateAvailable = false;
if (app.locals.versionRevLong === app.locals.versionRevLatest) {
log.core.info(`Running Latest Version (${app.locals.versionRevLong}; ${app.locals.version})`);
} else {
log.core.info(`Running Version: ${app.locals.versionRevLong}; ${app.locals.version} (Latest: ${app.locals.versionRevLatest})`);
app.locals.versionUpdateAvailable = true;
}
// ETA Init
const eta = new Eta({ views: path.join(__path, "views") })
app.engine("eta", buildEtaEngine())

View File

@ -0,0 +1,116 @@
import { Request, Response } from 'express';
import db, { handlePrismaError } from '../../../handlers/db.js'; // Database
import log from '../../../handlers/log.js';
///api/v1/alertContacts?action=count&filter=...
// GET without args -> Get all alertContacts
/**
* A function to create a sortBy compatible object from a string
*
* @export
* @param {string} SortField
* @param {string} Order
* @returns {object}
*/
export function parseDynamicSortBy(SortField: string, Order: string) {
return JSON.parse(`{ "${SortField}": "${Order}" }`);
}
/**
* Function to parse a string into a number or return undefined if it is not a number
*
* @export
* @param {string || any} data
* @returns {object}
*/
export function parseIntOrUndefined(data: any) {
return isNaN(parseInt(data)) ? undefined : parseInt(data);
}
// GET AlertContact
async function get(req: Request, res: Response) {
// Set sane defaults if undefined for sort
if (req.query.sort === undefined) {
req.query.sort = 'id';
}
if (req.query.order === undefined) {
req.query.order = 'asc';
}
// Prio 1 -> Get count (with or without filter)
// Prio 2 -> Get by id
// Prio 3 -> Get with filter
if ((req.query.search !== undefined && req.query.search.length > 0) || (req.query.id !== undefined && req.query.id.length > 0)) {
if (req.query.search !== undefined && req.query.search === '*') {
log.db.debug('Single * does not work with FullTextSearch');
req.query.search = '';
}
// When an ID is set, remove(disable) the search query
if (req.query.id !== undefined && req.query.id.length > 0) {
req.query.search = undefined;
}
const query = {
where: {
OR: [{ id: parseIntOrUndefined(req.query.id) }, { name: { search: req.query.search } }, { phone: { search: req.query.search } }, { comment: { search: req.query.search } }]
},
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString())
};
if (req.query.count === undefined) {
// get all entrys
await db.alertContacts.findMany(query).then((result) => {
res.status(200).json(result);
});
} else {
// count all entrys (filtered or not)
await db.alertContacts.count(query).then((result) => {
res.status(200).json(result);
});
}
} else {
if (req.query.count === undefined) {
await db.alertContacts.findMany({ orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString()) }).then((result) => {
res.status(200).json(result);
});
} else {
await db.alertContacts.count().then((result) => {
res.status(200).json(result);
});
}
}
}
// CREATE AlertContact
async function post(req: Request, res: Response) {
// Check if undefined or null
if (req.body.name != null && req.body.phone != null) {
await db.alertContacts
.create({
data: {
name: req.body.name,
phone: req.body.phone,
comment: req.body.comment,
},
select: {
id: true
}
}).then((result) => {
res.status(201).json({ status: 'CREATED', message: 'Successfully created alertContact', id: result.id });
})
} else {
res.status(400).json({ status: 'ERROR', errorcode: "VALIDATION_ERROR", message: 'One or more required fields are missing or invalid' });
}
}
// UPDATE AlertContact
async function patch(req: Request, res: Response) {}
// DELETE AlertContact
async function del(req: Request, res: Response) {}
export default { get, post, patch, del };

View File

@ -3,12 +3,12 @@ import passport from 'passport';
// Route imports
import testRoute from './test.js';
//import itemRoute from './items.js';
import alertContactsRoute from './alertContacts.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 versionRoute from './version.js'
//import search_routes from './search/index.js';
@ -25,14 +25,15 @@ Router.use('*', function (req, res, next) {
next();
});
//Router.route('/items').get(itemRoute.get).post(itemRoute.post).patch(itemRoute.patch).delete(itemRoute.del);
// All api routes lowercase! Yea I know but when strict: true it matters.
Router.route('/alertcontacts').get(alertContactsRoute.get).post(alertContactsRoute.post).patch(alertContactsRoute.patch).delete(alertContactsRoute.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.route('/version').get(versionRoute.get);
//Router.use('/search', search_routes);
Router.route('/test').get(testRoute.get);

View File

@ -0,0 +1,9 @@
import { Request, Response } from 'express';
function get(req: Request, res: Response) {
res.status(200).send({ version: '1.0.0', commit: req.app.locals.versionRev, updateAvailable: req.app.locals.versionUpdateAvailable });
};
export default { get };
// TODO: FIXME!!!!!!

View File

@ -5,6 +5,7 @@ import express from 'express';
// import skuRouteDash from './itemInfo.js'
// import testRoute from './test.js';
import dashboardRoute from './dashboard.js';
import testRoute from './test.js';
// import itemsRoute from './items.js';
// import manage_routes from './manage/index.js';
@ -20,5 +21,6 @@ const Router = express.Router({ strict: false });
// Router.use('/manage', manage_routes);
Router.route('/').get(dashboardRoute.get);
Router.route('/dbTest').get(testRoute.get);
export default Router;

View File

@ -0,0 +1,7 @@
import express, { Request, Response } from 'express';
function get(req: Request, res: Response) {
res.render("test", { message: "Hello world from eta!" })
}
export default { get };

View File

@ -17,6 +17,7 @@ const Router = express.Router({ strict: false });
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')));
Router.use('/libs/bootstrap-icons', express.static(path.join(__path, 'node_modules', 'bootstrap-icons')));
// Other routers
Router.use('/api', checkAuthentication, api_routes);