Compare commits
No commits in common. "50ad684ad3cd3831a6fd25183787e8b6f718545b" and "551f72f3e0e3f47886406753bf9deea95e378e3d" have entirely different histories.
50ad684ad3
...
551f72f3e0
@ -17,13 +17,12 @@ 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[]
|
||||||
|
|
||||||
@@fulltext([name])
|
@@fulltext([name])
|
||||||
}
|
}
|
||||||
|
|
||||||
model transactions {
|
model transactions {
|
||||||
id Int @id @unique @default(autoincrement())
|
id Int @id @unique @default(autoincrement())
|
||||||
@ -63,5 +62,3 @@ model products {
|
|||||||
|
|
||||||
@@fulltext([name])
|
@@fulltext([name])
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: migrate all ids to uuid?
|
|
||||||
|
@ -17,6 +17,7 @@ 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 });
|
||||||
@ -43,4 +44,4 @@ export function handlePrismaError(errorObj: any, res: Response, source: string)
|
|||||||
|
|
||||||
export default prisma;
|
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
|
// 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({ useTempFiles: false, debug: config.global.devmode }));
|
//app.use(fileUpload());
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
@ -92,6 +92,7 @@ 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,12 +1,10 @@
|
|||||||
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) {
|
||||||
@ -26,11 +24,9 @@ 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'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -40,49 +36,35 @@ async function get(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: CREATE/UPDATE image (upload)
|
// MARK: CREATE 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 {
|
||||||
// Check if multipart has image with file
|
log.api?.debug('POST image Success:', req.query, value);
|
||||||
if (!req.files || !req.files.image) {
|
|
||||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'Missing image' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const upload_file = req.files.image as UploadedFile;
|
// if (!req.files || Object.keys(req.files).length === 0) {
|
||||||
const upload_path = path.join(__path, 'images', `${value.id}.png`);
|
// res.status(400).send('No files were uploaded.');
|
||||||
const allowedMimeTypes = ['image/png'];
|
// }
|
||||||
|
|
||||||
// Check if mimetype is allowed
|
// const upload_file = req.files?.file;
|
||||||
if (!allowedMimeTypes.includes(upload_file.mimetype)) {
|
// let upload_path;
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
// //uploadPath = __dirname + '/somewhere/on/your/server/' + sampleFile.name;
|
||||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified product' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write file
|
// const allowedMimeTypes = ['image/png', 'image/jpeg'];
|
||||||
upload_file.mv(upload_path, (err) => {
|
// // if (!allowedMimeTypes.includes(upload_file.mimetype)) {
|
||||||
if (err) {
|
// // return res.status(400).send('Invalid file type. Only PNG and JPEG are allowed.');
|
||||||
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) => {
|
||||||
log.api?.debug('File uploaded to:', upload_path);
|
// if (err) {
|
||||||
res.status(200).json({ status: 'CREATED', message: 'Successfully uploaded image', id: result.id });
|
// return res.status(500).send(err);
|
||||||
});
|
// }
|
||||||
|
|
||||||
|
// res.send('File uploaded!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,34 +76,18 @@ 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);
|
||||||
const del_path = path.join(__path, 'images', `${value.id}.png`);
|
// await db.products
|
||||||
await db.products
|
// .delete({
|
||||||
.findUnique({
|
// where: {
|
||||||
where: {
|
// id: value.id
|
||||||
id: value.id
|
// }
|
||||||
}
|
// })
|
||||||
})
|
// .then((result) => {
|
||||||
.then((result) => {
|
// res.status(200).json({ status: 'DELETED', message: 'Successfully deleted product', id: result.id });
|
||||||
if (result) {
|
// })
|
||||||
if (!fs.existsSync(del_path)) {
|
// .catch((err) => {
|
||||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Product exists but no image found' });
|
// handlePrismaError(err, res, 'DEL products');
|
||||||
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,24 +3,22 @@ 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).default(0).note('product id') // id 0 should never exist, since id autoincrement starts at 1
|
id: validator.number().positive().precision(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
// MARK: CREATE / UPDATE image
|
// MARK: CREATE image
|
||||||
const schema_post = validator.object({
|
const schema_post = validator.object({
|
||||||
id: validator.number().positive().precision(0).required().note('product id')
|
id: validator.number().positive().precision(0)
|
||||||
//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).required().note('product id')
|
id: validator.number().positive().precision(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
||||||
@ -28,7 +26,6 @@ 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,16 +29,6 @@ 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);
|
||||||
@ -49,8 +39,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).patch(image_route.post).delete(image_route.del); // POST and PATCH are handled in 'image_route.post'
|
Router.route('/image').get(image_route.get).post(image_route.post).delete(image_route.del);
|
||||||
Router.route('/image/describe').get(image_schema);
|
Router.route('/image/describe').get(image_schema); // TODO: Checken, ob das probleme mit dem image_provider endpoint macht.
|
||||||
|
|
||||||
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 user' });
|
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified object' });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.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 any users' });
|
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified object' });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -97,7 +97,6 @@ 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: {
|
||||||
@ -128,7 +127,6 @@ 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,7 +22,6 @@ 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]+$/))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,10 +30,9 @@ 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', 'email', 'code');
|
.or('name', 'code');
|
||||||
|
|
||||||
// MARK: DELETE user
|
// MARK: DELETE user
|
||||||
const schema_del = validator.object({
|
const schema_del = validator.object({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user