Migrated validation to joi / Moved prisma_helpers to dedicated file /
This commit is contained in:
		
							
								
								
									
										22
									
								
								src/helpers/prisma_helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/helpers/prisma_helpers.ts
									
									
									
									
									
										Normal 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);
 | 
			
		||||
}
 | 
			
		||||
@@ -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 };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/routes/api/v1/alertContacts_schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/routes/api/v1/alertContacts_schema.ts
									
									
									
									
									
										Normal 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 }
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user