custom validation -> parse arrays from string / Rough transaction implementaion
This commit is contained in:
parent
bd43f03507
commit
fe04ad9ce3
7
package-lock.json
generated
7
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"bulma": "^1.0.3",
|
||||
@ -587,6 +588,12 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/bourne": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
|
||||
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
|
@ -33,6 +33,7 @@
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"bulma": "^1.0.3",
|
||||
|
23
src/handlers/validation.ts
Normal file
23
src/handlers/validation.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import Bourne from '@hapi/bourne';
|
||||
import Joi from 'joi';
|
||||
|
||||
const validator = Joi.extend((joi) => ({
|
||||
type: 'array',
|
||||
base: Joi.array(),
|
||||
coerce: {
|
||||
from: 'string',
|
||||
method(value, helpers) {
|
||||
if (typeof value !== 'string' || (value[0] !== '[' && !/^\s*\[/.test(value))) {
|
||||
return { value };
|
||||
}
|
||||
|
||||
try {
|
||||
return { value: Bourne.parse(value) };
|
||||
} catch (ignoreErr) {
|
||||
return { value };
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
export default validator;
|
@ -16,6 +16,9 @@ import products_schema from './products/products_schema.js';
|
||||
import image_route from './image/image.js';
|
||||
import image_schema from './image/image_schema.js';
|
||||
|
||||
import transaction_route from './transaction/transaction.js';
|
||||
import transaction_schema from './transaction/transaction_schema.js';
|
||||
|
||||
// Router base is '/api/v1'
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
@ -52,6 +55,9 @@ 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/describe').get(image_schema);
|
||||
|
||||
Router.route('/transaction').get(transaction_route.get).post(transaction_route.post).patch(transaction_route.patch).delete(transaction_route.del);
|
||||
Router.route('/transaction/describe').get(transaction_schema);
|
||||
|
||||
Router.route('/version').get(versionRoute.get);
|
||||
Router.route('/test').get(testRoute.get);
|
||||
|
||||
|
204
src/routes/api/v1/transaction/transaction.ts
Normal file
204
src/routes/api/v1/transaction/transaction.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import { Request, Response } from 'express';
|
||||
import db, { handlePrismaError } from '../../../../handlers/db.js'; // Database
|
||||
import log from '../../../../handlers/log.js';
|
||||
import { parseDynamicSortBy } from '../../../../helpers/prisma_helpers.js';
|
||||
import { schema_get, schema_post, schema_patch, schema_del } from './transaction_schema.js';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
// MARK: GET transaction
|
||||
async function get(req: Request, res: Response) {
|
||||
const { error, value } = schema_get.validate(req.query);
|
||||
if (error) {
|
||||
log.api?.debug('GET transaction 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('GET transaction Success:', req.query, value);
|
||||
|
||||
if (value.id !== undefined || value.user_id !== undefined) {
|
||||
// get by id or user_id
|
||||
await db
|
||||
.$transaction([
|
||||
// Same query for count and findMany
|
||||
db.transactions.count({
|
||||
where: {
|
||||
OR: [{ id: value.id }, { userId: value.user_id }],
|
||||
paid: value.paid
|
||||
},
|
||||
orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()),
|
||||
skip: value.skip,
|
||||
take: value.take
|
||||
}),
|
||||
db.transactions.findMany({
|
||||
where: {
|
||||
OR: [{ id: value.id }, { userId: value.user_id }],
|
||||
paid: value.paid
|
||||
},
|
||||
orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()),
|
||||
skip: value.skip,
|
||||
take: value.take
|
||||
})
|
||||
])
|
||||
.then(([count, result]) => {
|
||||
if (result.length !== 0) {
|
||||
res.status(200).json({ count, result });
|
||||
} else {
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified transaction' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
handlePrismaError(err, res, 'GET transaction');
|
||||
});
|
||||
} else {
|
||||
// get all
|
||||
await db
|
||||
.$transaction([
|
||||
// Same query for count and findMany
|
||||
db.transactions.count({
|
||||
where: {
|
||||
paid: value.paid
|
||||
},
|
||||
orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()),
|
||||
skip: value.skip,
|
||||
take: value.take
|
||||
}),
|
||||
db.transactions.findMany({
|
||||
where: {
|
||||
paid: value.paid
|
||||
},
|
||||
orderBy: parseDynamicSortBy(value.sort.toString(), value.order.toString()),
|
||||
skip: value.skip,
|
||||
take: value.take
|
||||
})
|
||||
])
|
||||
.then(([count, result]) => {
|
||||
if (result.length !== 0) {
|
||||
res.status(200).json({ count, result });
|
||||
} else {
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_FOUND', message: 'Could not find specified transaction' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
handlePrismaError(err, res, 'GET transaction');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CREATE transaction
|
||||
async function post(req: Request, res: Response) {
|
||||
const { error, value } = schema_post.validate(req.body);
|
||||
if (error) {
|
||||
log.api?.debug('POST transaction Error:', req.body, value, error.details[0].message);
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message });
|
||||
} else {
|
||||
log.api?.debug('POST transaction Success:', req.body, value);
|
||||
|
||||
const products: Array<number> = value.products;
|
||||
let total = new Prisma.Decimal(0);
|
||||
const salesData: { productId: number; price: number }[] = [];
|
||||
|
||||
try {
|
||||
// Start Prisma transaction
|
||||
await db.$transaction(async (prisma) => {
|
||||
for (let i = 0; i < products.length; i++) {
|
||||
log.api?.debug('Product:', products[i]);
|
||||
const product = await prisma.products.findUnique({
|
||||
where: { id: products[i] },
|
||||
select: { price: true }
|
||||
});
|
||||
|
||||
if (product) {
|
||||
log.api?.debug('Price:', product.price, Number(product.price));
|
||||
//total += Number(product.price);
|
||||
total = total.add(product.price);
|
||||
|
||||
salesData.push({
|
||||
productId: products[i],
|
||||
price: Number(product.price)
|
||||
});
|
||||
} else {
|
||||
log.api?.debug('Product not found:', products[i]);
|
||||
}
|
||||
}
|
||||
|
||||
log.api?.debug('Total:', total.toFixed(2));
|
||||
|
||||
// TODO: Check if user exists
|
||||
|
||||
// Create transaction with sales
|
||||
const transaction = await prisma.transactions.create({
|
||||
data: {
|
||||
userId: value.user_id,
|
||||
total: total,
|
||||
paid: false,
|
||||
sales: {
|
||||
create: salesData
|
||||
}
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
|
||||
res.status(201).json({ status: 'CREATED', message: 'Successfully created transaction', id: transaction.id });
|
||||
});
|
||||
} catch (err) {
|
||||
handlePrismaError(err, res, 'POST transaction');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UPDATE transaction
|
||||
async function patch(req: Request, res: Response) {
|
||||
const { error, value } = schema_patch.validate(req.body);
|
||||
if (error) {
|
||||
log.api?.debug('PATCH transaction Error:', req.body, value, error.details[0].message);
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message });
|
||||
} else {
|
||||
log.api?.debug('PATCH transaction Success:', req.body, value);
|
||||
await db.transactions
|
||||
.update({
|
||||
where: {
|
||||
id: value.id
|
||||
},
|
||||
data: {
|
||||
userId: value.user_id,
|
||||
paid: value.paid
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
res.status(200).json({ status: 'UPDATED', message: 'Successfully updated transaction', id: result.id });
|
||||
})
|
||||
.catch((err) => {
|
||||
handlePrismaError(err, res, 'PATCH transaction');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: DELETE transaction
|
||||
async function del(req: Request, res: Response) {
|
||||
const { error, value } = schema_del.validate(req.body);
|
||||
if (error) {
|
||||
log.api?.debug('DEL transaction Error:', req.body, value, error.details[0].message);
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: error.details[0].message });
|
||||
} else {
|
||||
log.api?.debug('DEL transaction Success:', req.body, value);
|
||||
await db.transactions
|
||||
.delete({
|
||||
where: {
|
||||
id: value.id
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
res.status(200).json({ status: 'DELETED', message: 'Successfully deleted transaction', id: result.id });
|
||||
})
|
||||
.catch((err) => {
|
||||
handlePrismaError(err, res, 'DEL transaction');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default { get, post, patch, del };
|
58
src/routes/api/v1/transaction/transaction_schema.ts
Normal file
58
src/routes/api/v1/transaction/transaction_schema.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { Request, Response } from 'express';
|
||||
//import validator from 'joi'; // DOCS: https://joi.dev/api
|
||||
import validator from '../../../../handlers/validation.js';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
// MARK: GET transaction
|
||||
const schema_get = validator
|
||||
.object({
|
||||
sort: validator
|
||||
.string()
|
||||
.valid(...Object.keys(Prisma.TransactionsScalarFieldEnum))
|
||||
.default('id'),
|
||||
|
||||
order: validator.string().valid('asc', 'desc').default('asc'),
|
||||
take: validator.number().min(1).max(512),
|
||||
skip: validator.number().min(0),
|
||||
|
||||
id: validator.number().positive().precision(0),
|
||||
user_id: validator.number().positive().precision(0),
|
||||
paid: validator.boolean().note('true-> Only paid / false-> Only unpaid / undefined-> both')
|
||||
})
|
||||
.nand('id', 'user_id'); // Allow id or user_id. not both.
|
||||
|
||||
// MARK: CREATE transaction
|
||||
const schema_post = validator.object({
|
||||
products: validator.array().items(validator.number().positive().precision(0)).required(),
|
||||
user_id: validator.number().positive().precision(0).required(),
|
||||
paid: validator.boolean().default(false)
|
||||
});
|
||||
|
||||
// MARK: UPDATE transaction
|
||||
const schema_patch = validator.object({
|
||||
user_id: validator.number().positive().precision(0).required(),
|
||||
paid: validator.boolean().default(false)
|
||||
});
|
||||
|
||||
// MARK: DELETE transaction
|
||||
const schema_del = validator.object({
|
||||
id: validator.number().positive().precision(0).required()
|
||||
});
|
||||
|
||||
// Describe all schemas
|
||||
const schema_get_desc = schema_get.describe();
|
||||
const schema_post_desc = schema_post.describe();
|
||||
const schema_patch_desc = schema_patch.describe();
|
||||
const schema_del_desc = schema_del.describe();
|
||||
|
||||
// GET route
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
export { schema_get, schema_post, schema_patch, schema_del };
|
Loading…
x
Reference in New Issue
Block a user