Migrated validation to joi / Moved prisma_helpers to dedicated file /

This commit is contained in:
Leon Meier 2025-01-30 00:24:59 +01:00
parent be1544a46f
commit c96bdfddb0
4 changed files with 138 additions and 99 deletions

View File

@ -0,0 +1,22 @@
/**
* 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);
}

View File

@ -1,116 +1,87 @@
import { Request, Response } from 'express';
import db, { handlePrismaError } from '../../../handlers/db.js'; // Database
import log from '../../../handlers/log.js';
import { parseIntOrUndefined, parseDynamicSortBy } from '../../../helpers/prisma_helpers.js';
import { schema_get } from './alertContacts_schema.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
// MARK: 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';
}
const { error, value } = schema_get.validate(req.query);
if (error) {
log.api?.debug('Error:', req.query, value);
res.status(400).json({ error: error.details[0].message });
} else {
log.api?.debug('Success:', req.query, value);
// 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 = {
// Query with FullTextSearch
const query_fts = {
where: {
OR: [{ id: parseIntOrUndefined(req.query.id) }, { name: { search: req.query.search } }, { phone: { search: req.query.search } }, { comment: { search: req.query.search } }]
OR: [{ id: parseIntOrUndefined(value.id) }, { name: { search: value.search } }, { phone: { search: value.search } }, { comment: { search: value.search } }]
},
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString())
orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString())
};
if (req.query.count === undefined) {
// get all entrys
await db.alertContacts.findMany(query).then((result) => {
res.status(200).json(result);
});
if (value.search !== undefined || value.id !== undefined) {
// with FullTextSearch
if (!value.count) {
// get all entrys
log.api?.trace('get all entrys - with FullTextSearch');
await db.alertContacts.findMany(query_fts).then((result) => {
res.status(200).json(result);
});
} else {
// count all entrys
log.api?.trace('count all entrys - with FullTextSearch');
await db.alertContacts.count(query_fts).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);
});
// without FullTextSearch
if (!value.count) {
// get all entrys
log.api?.trace('get all entrys - without FullTextSearch');
await db.alertContacts.findMany({ orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()) }).then((result) => {
res.status(200).json(result);
});
} else {
// count all entrys without FullTextSearch
log.api?.trace('count all entrys - without FullTextSearch');
await db.alertContacts.count().then((result) => {
res.status(200).json(result);
});
}
}
}
}
// CREATE AlertContact
// MARK: 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 });
})
.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' });
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing or invalid' });
}
}
// UPDATE AlertContact
// MARK: UPDATE AlertContact
async function patch(req: Request, res: Response) {}
// DELETE AlertContact
// MARK: DELETE AlertContact
async function del(req: Request, res: Response) {}
export default { get, post, patch, del };

View File

@ -0,0 +1,53 @@
import { Request, Response } from 'express';
import validator from 'joi'; // DOCS: https://joi.dev/api
import log from '../../../handlers/log.js';
const schema_get = validator.object({
sort: validator.string().valid('id', 'name', 'phone', 'comment').default('id'),
order: validator.string().valid('asc', 'desc').default('asc'),
search: validator.string().min(3).max(20), // TODO: Check if * or ** or *** -> Due to crashes..
id: validator.number().positive().precision(0),
count: validator.boolean()
}).nand('id', 'search'); // Allow id or search. not both.
const schema_post = validator.object({
name: validator.string().min(1).max(32).required(),
phone: validator.string().pattern(new RegExp('^\\+(\\d{1,3})\\s*(?:\\(\\s*(\\d{2,5})\\s*\\)|\\s*(\\d{2,5})\\s*)\\s*(\\d{5,15})$')).required(),
comment: validator.string().max(64),
})
const schema_patch = validator.object({
sort: validator.string().valid('id', 'name', 'phone', 'comment').default('id'),
order: validator.string().valid('asc', 'desc').default('asc'),
search: validator.string().min(3).max(20), // TODO: Check if * or ** or *** -> Due to crashes..
id: validator.number().positive().precision(0),
count: validator.boolean()
}).nand('id', 'search'); // Allow id or search. not both.
const schema_del = validator.object({
sort: validator.string().valid('id', 'name', 'phone', 'comment').default('id'),
order: validator.string().valid('asc', 'desc').default('asc'),
search: validator.string().min(3).max(20), // TODO: Check if * or ** or *** -> Due to crashes..
id: validator.number().positive().precision(0),
count: validator.boolean()
}).nand('id', 'search'); // Allow id or search. not both.
// Describe all schemas
const schema_get_describe = schema_get.describe();
const schema_post_describe = schema_post.describe();
const schema_patch_describe = schema_patch.describe();
const schema_del_describe = schema_del.describe();
// GET route
export default async function get(req: Request, res: Response) {
res.status(200).json({
GET: schema_get_describe,
POST: schema_post_describe,
PATCH: schema_patch_describe,
DELETE: schema_del_describe
});
}
export { schema_get, schema_post, schema_patch, schema_del }

View File

@ -3,14 +3,11 @@ import passport from 'passport';
// Route imports
import testRoute from './test.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 search_routes from './search/index.js';
import alertContactsRoute from './alertContacts.js';
import alertContactsRoute_schema from './alertContacts_schema.js';
// Router base is '/api/v1'
const Router = express.Router({ strict: false });
@ -27,11 +24,7 @@ Router.use('*', function (req, res, next) {
// 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('/alertcontacts/describe').get(alertContactsRoute_schema);
Router.route('/version').get(versionRoute.get);
//Router.use('/search', search_routes);