Compare commits
2 Commits
551f72f3e0
...
50ad684ad3
Author | SHA1 | Date | |
---|---|---|---|
50ad684ad3 | |||
0233196276 |
@ -17,12 +17,13 @@ model user {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
name String @unique
|
||||
code String?
|
||||
email String?
|
||||
|
||||
// TODO: Prüfen ob nötig, erstmal vorbereitet.
|
||||
transactions transactions[]
|
||||
|
||||
@@fulltext([name])
|
||||
}
|
||||
}
|
||||
|
||||
model transactions {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
@ -62,3 +63,5 @@ model products {
|
||||
|
||||
@@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);
|
||||
if (errorObj instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
switch (errorObj.code) {
|
||||
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
case 'P2002':
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'DB_ERROR', message: 'The object needs to be unique', meta: errorObj.meta });
|
||||
@ -44,4 +43,4 @@ export function handlePrismaError(errorObj: any, res: Response, source: string)
|
||||
|
||||
export default prisma;
|
||||
|
||||
//FIXME: https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/null-and-undefined
|
||||
//FIXME: https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/null-and-undefined
|
||||
|
@ -11,7 +11,7 @@ import config from './handlers/config.js';
|
||||
// Express & more
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
//import fileUpload from 'express-fileupload';
|
||||
import fileUpload from 'express-fileupload';
|
||||
import bodyParser from 'body-parser';
|
||||
import { Eta, Options } from 'eta';
|
||||
|
||||
@ -78,7 +78,7 @@ if (!config.global.devmode) {
|
||||
); // Add headers
|
||||
}
|
||||
|
||||
//app.use(fileUpload());
|
||||
app.use(fileUpload({ useTempFiles: false, debug: config.global.devmode }));
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
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);
|
||||
config.global.devmode && log.core.error('DEVMODE ACTIVE! Do NOT use this in prod!');
|
||||
|
||||
|
||||
// MARK: Helper Functions
|
||||
function buildEtaEngine() {
|
||||
return (path: string, opts: Options, callback: CallableFunction) => {
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Request, Response } from 'express';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import db, { handlePrismaError } from '../../../../handlers/db.js'; // Database
|
||||
import log from '../../../../handlers/log.js';
|
||||
import __path from '../../../../handlers/path.js';
|
||||
import { schema_get, schema_post, schema_del } from './image_schema.js';
|
||||
import { UploadedFile } from 'express-fileupload';
|
||||
|
||||
// MARK: GET image
|
||||
async function get(req: Request, res: Response) {
|
||||
@ -24,9 +26,11 @@ async function get(req: Request, res: Response) {
|
||||
if (result) {
|
||||
const img_path = path.join(__path, 'images', `${value.id}.png`);
|
||||
// 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'));
|
||||
} else {
|
||||
// Product does not exist
|
||||
log.api?.debug('Product does not exist, using default image ');
|
||||
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) {
|
||||
const { error, value } = schema_post.validate(req.query);
|
||||
if (error) {
|
||||
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 });
|
||||
} 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) {
|
||||
// res.status(400).send('No files were uploaded.');
|
||||
// }
|
||||
const upload_file = req.files.image as UploadedFile;
|
||||
const upload_path = path.join(__path, 'images', `${value.id}.png`);
|
||||
const allowedMimeTypes = ['image/png'];
|
||||
|
||||
// const upload_file = req.files?.file;
|
||||
// let upload_path;
|
||||
// Check if mimetype is allowed
|
||||
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'];
|
||||
// // if (!allowedMimeTypes.includes(upload_file.mimetype)) {
|
||||
// // return res.status(400).send('Invalid file type. Only PNG and JPEG are allowed.');
|
||||
// // }
|
||||
|
||||
// uploadedFile.mv(`/path/to/save/${uploadedFile.name}`, (err) => {
|
||||
// if (err) {
|
||||
// return res.status(500).send(err);
|
||||
// }
|
||||
|
||||
// res.send('File uploaded!');
|
||||
// Write file
|
||||
upload_file.mv(upload_path, (err) => {
|
||||
if (err) {
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'IO_ERROR', message: 'Could not write image to disk' });
|
||||
return;
|
||||
}
|
||||
log.api?.debug('File uploaded to:', upload_path);
|
||||
res.status(200).json({ status: 'CREATED', message: 'Successfully uploaded image', id: result.id });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
} else {
|
||||
log.api?.debug('DEL image Success:', req.query, value);
|
||||
// await db.products
|
||||
// .delete({
|
||||
// where: {
|
||||
// id: value.id
|
||||
// }
|
||||
// })
|
||||
// .then((result) => {
|
||||
// res.status(200).json({ status: 'DELETED', message: 'Successfully deleted product', id: result.id });
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// handlePrismaError(err, res, 'DEL products');
|
||||
// });
|
||||
const del_path = path.join(__path, 'images', `${value.id}.png`);
|
||||
await db.products
|
||||
.findUnique({
|
||||
where: {
|
||||
id: value.id
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
if (!fs.existsSync(del_path)) {
|
||||
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
|
||||
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({
|
||||
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
|
||||
const schema_del = validator.object({
|
||||
id: validator.number().positive().precision(0)
|
||||
id: validator.number().positive().precision(0).required().note('product id')
|
||||
});
|
||||
|
||||
// Describe all schemas
|
||||
const schema_get_desc = schema_get.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();
|
||||
|
||||
// GET route
|
||||
@ -26,6 +28,7 @@ export default async function get(req: Request, res: Response) {
|
||||
res.status(200).json({
|
||||
GET: schema_get_desc,
|
||||
POST: schema_post_desc,
|
||||
PATCH: schema_patch_desc,
|
||||
DELETE: schema_del_desc
|
||||
});
|
||||
}
|
||||
|
@ -29,6 +29,16 @@ Router.use('*', function (req, res, 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.
|
||||
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);
|
||||
@ -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/describe').get(products_schema);
|
||||
|
||||
Router.route('/image').get(image_route.get).post(image_route.post).delete(image_route.del);
|
||||
Router.route('/image/describe').get(image_schema); // TODO: Checken, ob das probleme mit dem image_provider endpoint macht.
|
||||
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);
|
||||
|
||||
Router.route('/version').get(versionRoute.get);
|
||||
Router.route('/test').get(testRoute.get);
|
||||
|
@ -43,7 +43,7 @@ async function get(req: Request, res: Response) {
|
||||
});
|
||||
res.status(200).json({ count, result });
|
||||
} 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) => {
|
||||
@ -75,7 +75,7 @@ async function get(req: Request, res: Response) {
|
||||
});
|
||||
res.status(200).json({ count, result });
|
||||
} 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) => {
|
||||
@ -97,6 +97,7 @@ async function post(req: Request, res: Response) {
|
||||
.create({
|
||||
data: {
|
||||
name: value.name,
|
||||
email: value.email,
|
||||
code: value.code === '0000' ? null : value.code
|
||||
},
|
||||
select: {
|
||||
@ -127,6 +128,7 @@ async function patch(req: Request, res: Response) {
|
||||
},
|
||||
data: {
|
||||
name: value.name,
|
||||
email: value.email,
|
||||
code: value.code === '0000' ? null : value.code
|
||||
},
|
||||
select: {
|
||||
|
@ -22,6 +22,7 @@ const schema_get = validator
|
||||
// MARK: CREATE user
|
||||
const schema_post = validator.object({
|
||||
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]+$/))
|
||||
});
|
||||
|
||||
@ -30,9 +31,10 @@ const schema_patch = validator
|
||||
.object({
|
||||
id: validator.number().positive().precision(0).required(),
|
||||
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]+$/))
|
||||
})
|
||||
.or('name', 'code');
|
||||
.or('name', 'email', 'code');
|
||||
|
||||
// MARK: DELETE user
|
||||
const schema_del = validator.object({
|
||||
|
Loading…
x
Reference in New Issue
Block a user