Compare commits
	
		
			2 Commits
		
	
	
		
			551f72f3e0
			...
			50ad684ad3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 50ad684ad3 | |||
| 0233196276 | 
| @@ -17,6 +17,7 @@ model user { | |||||||
|   id   Int    @id @unique @default(autoincrement()) |   id   Int    @id @unique @default(autoincrement()) | ||||||
|   name String @unique |   name String @unique | ||||||
|   code String? |   code String? | ||||||
|  |   email String? | ||||||
|  |  | ||||||
|   // TODO: Prüfen ob nötig, erstmal vorbereitet. |   // TODO: Prüfen ob nötig, erstmal vorbereitet. | ||||||
|   transactions transactions[] |   transactions transactions[] | ||||||
| @@ -62,3 +63,5 @@ model products { | |||||||
|  |  | ||||||
|   @@fulltext([name]) |   @@fulltext([name]) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: migrate all ids to uuid? | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ export function handlePrismaError(errorObj: any, res: Response, source: string) | |||||||
| 	log.db.error(source, errorObj); | 	log.db.error(source, errorObj); | ||||||
| 	if (errorObj instanceof Prisma.PrismaClientKnownRequestError) { | 	if (errorObj instanceof Prisma.PrismaClientKnownRequestError) { | ||||||
| 		switch (errorObj.code) { | 		switch (errorObj.code) { | ||||||
|  |  | ||||||
| 			// P2002 -> "Unique constraint failed on the {constraint}" | 			// P2002 -> "Unique constraint failed on the {constraint}" | ||||||
| 			case 'P2002': | 			case 'P2002': | ||||||
| 				res.status(409).json({ status: 'ERROR', errorcode: 'DB_ERROR', message: 'The object needs to be unique', meta: errorObj.meta }); | 				res.status(409).json({ status: 'ERROR', errorcode: 'DB_ERROR', message: 'The object needs to be unique', meta: errorObj.meta }); | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import config from './handlers/config.js'; | |||||||
| // Express & more | // Express & more | ||||||
| import express from 'express'; | import express from 'express'; | ||||||
| import helmet from 'helmet'; | import helmet from 'helmet'; | ||||||
| //import fileUpload from 'express-fileupload'; | import fileUpload from 'express-fileupload'; | ||||||
| import bodyParser from 'body-parser'; | import bodyParser from 'body-parser'; | ||||||
| import { Eta, Options } from 'eta'; | import { Eta, Options } from 'eta'; | ||||||
|  |  | ||||||
| @@ -78,7 +78,7 @@ if (!config.global.devmode) { | |||||||
| 	); // Add headers | 	); // Add headers | ||||||
| } | } | ||||||
|  |  | ||||||
| //app.use(fileUpload()); | app.use(fileUpload({ useTempFiles: false, debug: config.global.devmode })); | ||||||
| app.use(bodyParser.urlencoded({ extended: false })); | app.use(bodyParser.urlencoded({ extended: false })); | ||||||
| app.use(bodyParser.json()); | app.use(bodyParser.json()); | ||||||
|  |  | ||||||
| @@ -92,7 +92,6 @@ app.listen(config.global.http_port, config.global.http_listen_address, () => { | |||||||
| log.core.trace('Running from path: ' + __path); | log.core.trace('Running from path: ' + __path); | ||||||
| config.global.devmode && log.core.error('DEVMODE ACTIVE! Do NOT use this in prod!'); | config.global.devmode && log.core.error('DEVMODE ACTIVE! Do NOT use this in prod!'); | ||||||
|  |  | ||||||
|  |  | ||||||
| // MARK: Helper Functions | // MARK: Helper Functions | ||||||
| function buildEtaEngine() { | function buildEtaEngine() { | ||||||
| 	return (path: string, opts: Options, callback: CallableFunction) => { | 	return (path: string, opts: Options, callback: CallableFunction) => { | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import path from 'node:path'; | import path from 'node:path'; | ||||||
| import fs from 'node:fs'; | import fs from 'node:fs'; | ||||||
|  |  | ||||||
| import db, { handlePrismaError } from '../../../../handlers/db.js'; // Database | import db, { handlePrismaError } from '../../../../handlers/db.js'; // Database | ||||||
| import log from '../../../../handlers/log.js'; | import log from '../../../../handlers/log.js'; | ||||||
| import __path from '../../../../handlers/path.js'; | import __path from '../../../../handlers/path.js'; | ||||||
| import { schema_get, schema_post, schema_del } from './image_schema.js'; | import { schema_get, schema_post, schema_del } from './image_schema.js'; | ||||||
|  | import { UploadedFile } from 'express-fileupload'; | ||||||
|  |  | ||||||
| // MARK: GET image | // MARK: GET image | ||||||
| async function get(req: Request, res: Response) { | async function get(req: Request, res: Response) { | ||||||
| @@ -24,9 +26,11 @@ async function get(req: Request, res: Response) { | |||||||
| 				if (result) { | 				if (result) { | ||||||
| 					const img_path = path.join(__path, 'images', `${value.id}.png`); | 					const img_path = path.join(__path, 'images', `${value.id}.png`); | ||||||
| 					// Serve stored or default image | 					// Serve stored or default image | ||||||
|  | 					log.api?.debug('Image exists:', fs.existsSync(img_path)); | ||||||
| 					fs.existsSync(img_path) ? res.sendFile(img_path) : res.sendFile(path.join(__path, 'images', 'default.png')); | 					fs.existsSync(img_path) ? res.sendFile(img_path) : res.sendFile(path.join(__path, 'images', 'default.png')); | ||||||
| 				} else { | 				} else { | ||||||
| 					// Product does not exist | 					// Product does not exist | ||||||
|  | 					log.api?.debug('Product does not exist, using default image '); | ||||||
| 					res.sendFile(path.join(__path, 'images', 'default.png')); | 					res.sendFile(path.join(__path, 'images', 'default.png')); | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| @@ -36,35 +40,49 @@ async function get(req: Request, res: Response) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // MARK: CREATE image (upload) | // MARK: CREATE/UPDATE image (upload) | ||||||
| async function post(req: Request, res: Response) { | async function post(req: Request, res: Response) { | ||||||
| 	const { error, value } = schema_post.validate(req.query); | 	const { error, value } = schema_post.validate(req.query); | ||||||
| 	if (error) { | 	if (error) { | ||||||
| 		log.api?.debug('POST image Error:', req.query, value, error.details[0].message); | 		log.api?.debug('POST image Error:', req.query, value, error.details[0].message); | ||||||
| 		res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); | 		res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); | ||||||
| 	} else { | 	} else { | ||||||
| 		log.api?.debug('POST image Success:', req.query, value); | 		// Check if multipart has image with file | ||||||
|  | 		if (!req.files || !req.files.image) { | ||||||
|  | 			res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'Missing image' }); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// 	if (!req.files || Object.keys(req.files).length === 0) { | 		const upload_file = req.files.image as UploadedFile; | ||||||
| 		// 		res.status(400).send('No files were uploaded.'); | 		const upload_path = path.join(__path, 'images', `${value.id}.png`); | ||||||
| 		// 	} | 		const allowedMimeTypes = ['image/png']; | ||||||
|  |  | ||||||
| 		// 	const upload_file = req.files?.file; | 		// Check if mimetype is allowed | ||||||
| 		// 	let upload_path; | 		if (!allowedMimeTypes.includes(upload_file.mimetype)) { | ||||||
|  | 			res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'Only image/png is allowed' }); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		// Check if product exists | ||||||
|  | 		const result = await db.products.findUnique({ | ||||||
|  | 			where: { | ||||||
|  | 				id: value.id | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		// 	//uploadPath = __dirname + '/somewhere/on/your/server/' + sampleFile.name; | 		if (!result) { | ||||||
|  | 			res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified product' }); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// 	const allowedMimeTypes = ['image/png', 'image/jpeg']; | 		// Write file | ||||||
| 		// 	// if (!allowedMimeTypes.includes(upload_file.mimetype)) { | 		upload_file.mv(upload_path, (err) => { | ||||||
| 		// 	// 	return res.status(400).send('Invalid file type. Only PNG and JPEG are allowed.'); | 			if (err) { | ||||||
| 		// 	// } | 				res.status(500).json({ status: 'ERROR', errorcode: 'IO_ERROR', message: 'Could not write image to disk' }); | ||||||
|  | 				return; | ||||||
| 		// 	uploadedFile.mv(`/path/to/save/${uploadedFile.name}`, (err) => { | 			} | ||||||
| 		// 		if (err) { | 			log.api?.debug('File uploaded to:', upload_path); | ||||||
| 		// 		    return res.status(500).send(err); | 			res.status(200).json({ status: 'CREATED', message: 'Successfully uploaded image', id: result.id }); | ||||||
| 		// 		} | 		}); | ||||||
|  |  | ||||||
| 		// 		res.send('File uploaded!'); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -76,18 +94,34 @@ async function del(req: Request, res: Response) { | |||||||
| 		res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); | 		res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message }); | ||||||
| 	} else { | 	} else { | ||||||
| 		log.api?.debug('DEL image Success:', req.query, value); | 		log.api?.debug('DEL image Success:', req.query, value); | ||||||
| 		// await db.products | 		const del_path = path.join(__path, 'images', `${value.id}.png`); | ||||||
| 		// 	.delete({ | 		await db.products | ||||||
| 		// 		where: { | 			.findUnique({ | ||||||
| 		// 			id: value.id | 				where: { | ||||||
| 		// 		} | 					id: value.id | ||||||
| 		// 	}) | 				} | ||||||
| 		// 	.then((result) => { | 			}) | ||||||
| 		// 		res.status(200).json({ status: 'DELETED', message: 'Successfully deleted product', id: result.id }); | 			.then((result) => { | ||||||
| 		// 	}) | 				if (result) { | ||||||
| 		// 	.catch((err) => { | 					if (!fs.existsSync(del_path)) { | ||||||
| 		// 		handlePrismaError(err, res, 'DEL products'); | 						res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Product exists but no image found' }); | ||||||
| 		// 	}); | 						return; | ||||||
|  | 					} | ||||||
|  | 					fs.unlink(del_path, (err) => { | ||||||
|  | 						if (err) { | ||||||
|  | 							res.status(500).json({ status: 'ERROR', errorcode: 'IO_ERROR', message: 'Could not delete image' }); | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 						res.status(200).json({ status: 'DELETED', message: 'Successfully deleted image', id: result.id }); | ||||||
|  | 						log.api?.debug('File removed from:', del_path); | ||||||
|  | 					}); | ||||||
|  | 				} else { | ||||||
|  | 					res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified product (image)' }); | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 			.catch((err) => { | ||||||
|  | 				handlePrismaError(err, res, 'DEL products'); | ||||||
|  | 			}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,22 +3,24 @@ import validator from 'joi'; // DOCS: https://joi.dev/api | |||||||
|  |  | ||||||
| // MARK: GET image | // MARK: GET image | ||||||
| const schema_get = validator.object({ | const schema_get = validator.object({ | ||||||
| 	id: validator.number().positive().precision(0) | 	id: validator.number().positive().precision(0).default(0).note('product id') // id 0 should never exist, since id autoincrement starts at 1 | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // MARK: CREATE image | // MARK: CREATE / UPDATE image | ||||||
| const schema_post = validator.object({ | const schema_post = validator.object({ | ||||||
| 	id: validator.number().positive().precision(0) | 	id: validator.number().positive().precision(0).required().note('product id') | ||||||
|  | 	//image: validator.string().required().note('product image as multipart/form-data') | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // MARK: DELETE products | // MARK: DELETE products | ||||||
| const schema_del = validator.object({ | const schema_del = validator.object({ | ||||||
| 	id: validator.number().positive().precision(0) | 	id: validator.number().positive().precision(0).required().note('product id') | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Describe all schemas | // Describe all schemas | ||||||
| const schema_get_desc = schema_get.describe(); | const schema_get_desc = schema_get.describe(); | ||||||
| const schema_post_desc = schema_post.describe(); | const schema_post_desc = schema_post.describe(); | ||||||
|  | const schema_patch_desc = schema_post.describe(); // Just for show (POST = PATCH) | ||||||
| const schema_del_desc = schema_del.describe(); | const schema_del_desc = schema_del.describe(); | ||||||
|  |  | ||||||
| // GET route | // GET route | ||||||
| @@ -26,6 +28,7 @@ export default async function get(req: Request, res: Response) { | |||||||
| 	res.status(200).json({ | 	res.status(200).json({ | ||||||
| 		GET: schema_get_desc, | 		GET: schema_get_desc, | ||||||
| 		POST: schema_post_desc, | 		POST: schema_post_desc, | ||||||
|  | 		PATCH: schema_patch_desc, | ||||||
| 		DELETE: schema_del_desc | 		DELETE: schema_del_desc | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,6 +29,16 @@ Router.use('*', function (req, res, next) { | |||||||
| 	next(); | 	next(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | // All empty strings are undefined (not null!) values (query) | ||||||
|  | Router.use('*', function (req, res, next) { | ||||||
|  | 	for (let key in req.query) { | ||||||
|  | 		if (req.query[key] === '') { | ||||||
|  | 			req.query[key] = undefined; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	next(); | ||||||
|  | }); | ||||||
|  |  | ||||||
| // All api routes lowercase! Yea I know but when strict: true it matters. | // All api routes lowercase! Yea I know but when strict: true it matters. | ||||||
| Router.route('/user').get(user_route.get).post(user_route.post).patch(user_route.patch).delete(user_route.del); | Router.route('/user').get(user_route.get).post(user_route.post).patch(user_route.patch).delete(user_route.del); | ||||||
| Router.route('/user/describe').get(user_schema); | Router.route('/user/describe').get(user_schema); | ||||||
| @@ -39,8 +49,8 @@ Router.route('/user/codecheck/describe').get(user_codecheck_schema); | |||||||
| Router.route('/products').get(products_route.get).post(products_route.post).patch(products_route.patch).delete(products_route.del); | Router.route('/products').get(products_route.get).post(products_route.post).patch(products_route.patch).delete(products_route.del); | ||||||
| Router.route('/products/describe').get(products_schema); | Router.route('/products/describe').get(products_schema); | ||||||
|  |  | ||||||
| Router.route('/image').get(image_route.get).post(image_route.post).delete(image_route.del); | Router.route('/image').get(image_route.get).post(image_route.post).patch(image_route.post).delete(image_route.del); // POST and PATCH are handled in 'image_route.post' | ||||||
| Router.route('/image/describe').get(image_schema); // TODO: Checken, ob das probleme mit dem image_provider endpoint macht. | Router.route('/image/describe').get(image_schema); | ||||||
|  |  | ||||||
| Router.route('/version').get(versionRoute.get); | Router.route('/version').get(versionRoute.get); | ||||||
| Router.route('/test').get(testRoute.get); | Router.route('/test').get(testRoute.get); | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ async function get(req: Request, res: Response) { | |||||||
| 						}); | 						}); | ||||||
| 						res.status(200).json({ count, result }); | 						res.status(200).json({ count, result }); | ||||||
| 					} else { | 					} else { | ||||||
| 						res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified object' }); | 						res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified user' }); | ||||||
| 					} | 					} | ||||||
| 				}) | 				}) | ||||||
| 				.catch((err) => { | 				.catch((err) => { | ||||||
| @@ -75,7 +75,7 @@ async function get(req: Request, res: Response) { | |||||||
| 						}); | 						}); | ||||||
| 						res.status(200).json({ count, result }); | 						res.status(200).json({ count, result }); | ||||||
| 					} else { | 					} else { | ||||||
| 						res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified object' }); | 						res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find any users' }); | ||||||
| 					} | 					} | ||||||
| 				}) | 				}) | ||||||
| 				.catch((err) => { | 				.catch((err) => { | ||||||
| @@ -97,6 +97,7 @@ async function post(req: Request, res: Response) { | |||||||
| 			.create({ | 			.create({ | ||||||
| 				data: { | 				data: { | ||||||
| 					name: value.name, | 					name: value.name, | ||||||
|  | 					email: value.email, | ||||||
| 					code: value.code === '0000' ? null : value.code | 					code: value.code === '0000' ? null : value.code | ||||||
| 				}, | 				}, | ||||||
| 				select: { | 				select: { | ||||||
| @@ -127,6 +128,7 @@ async function patch(req: Request, res: Response) { | |||||||
| 				}, | 				}, | ||||||
| 				data: { | 				data: { | ||||||
| 					name: value.name, | 					name: value.name, | ||||||
|  | 					email: value.email, | ||||||
| 					code: value.code === '0000' ? null : value.code | 					code: value.code === '0000' ? null : value.code | ||||||
| 				}, | 				}, | ||||||
| 				select: { | 				select: { | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ const schema_get = validator | |||||||
| // MARK: CREATE user | // MARK: CREATE user | ||||||
| const schema_post = validator.object({ | const schema_post = validator.object({ | ||||||
| 	name: validator.string().min(1).max(32).required(), | 	name: validator.string().min(1).max(32).required(), | ||||||
|  | 	email: validator.string().email().trim().required(), | ||||||
| 	code: validator.string().min(4).max(4).trim().regex(new RegExp(/^[0-9]+$/)) | 	code: validator.string().min(4).max(4).trim().regex(new RegExp(/^[0-9]+$/)) | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -30,9 +31,10 @@ const schema_patch = validator | |||||||
| 	.object({ | 	.object({ | ||||||
| 		id: validator.number().positive().precision(0).required(), | 		id: validator.number().positive().precision(0).required(), | ||||||
| 		name: validator.string().min(1).max(32), | 		name: validator.string().min(1).max(32), | ||||||
|  | 		email: validator.string().email().trim(), | ||||||
| 		code: validator.string().min(4).max(4).trim().regex(new RegExp(/^[0-9]+$/)) | 		code: validator.string().min(4).max(4).trim().regex(new RegExp(/^[0-9]+$/)) | ||||||
| 	}) | 	}) | ||||||
| 	.or('name', 'code'); | 	.or('name', 'email', 'code'); | ||||||
|  |  | ||||||
| // MARK: DELETE user | // MARK: DELETE user | ||||||
| const schema_del = validator.object({ | const schema_del = validator.object({ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user