266 lines
8.2 KiB
TypeScript

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';
class AbortError extends Error {
constructor(public http_status: number, public status: string, public errorcode: string, public message: string, public details?: any) {
super(message);
this.name = 'AbortError';
}
}
// 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;
const outOfStockProducts: { id: number; name: string }[] = [];
const notFoundProducts: { id: number; name: string }[] = [];
const salesData: { productId: number; price: number }[] = [];
let total = new Prisma.Decimal(0);
// Start Prisma transaction
await db
.$transaction(async (prisma) => {
// Iterate over all products for this transaction(not prisma)
for (let i = 0; i < products.length; i++) {
log.api?.debug('Product:', i + 1, 'of', products.length, '(Loop)');
// Get product (price, stock, name)
const product = await prisma.products.findUnique({
where: { id: products[i] },
select: { price: true, stock: true, name: true }
});
// Check if product exists
if (product) {
log.api?.debug('Price:', product.price, '[Name:' + product.name + ']', '[ID:' + products[i] + ']');
if (product.stock > 0) {
// Add price of current product to total
total = total.add(product.price);
// Add product to salesData -> Later generate sales entry for each product
salesData.push({
productId: products[i],
price: Number(product.price)
});
// Reduce stock by 1
await prisma.products.update({
where: { id: products[i] },
data: { stock: { decrement: 1 } }
});
} else {
// Product is out of stock
outOfStockProducts.push({ id: products[i], name: product.name });
}
} else {
// Product not found
notFoundProducts.push({ id: products[i], name: 'unknown' });
}
}
log.api?.debug('Total:', total.toFixed(2));
// Abort the Prisma transaction if there are not existing products
if (notFoundProducts.length > 0) {
log.api?.debug('Aborting. missing products:', notFoundProducts);
throw new AbortError(
400, // http_status
'ERROR', // status
'NOT_FOUND', // errorcode
'Some of the products included in the transaction do not exist, therefore the transaction has not been processed.', // message
notFoundProducts // details
);
}
// Abort the Prisma transaction if there are products with insufficient stock
if (outOfStockProducts.length > 0) {
log.api?.debug('Aborting. out of stock products:', outOfStockProducts);
throw new AbortError(
400, // http_status
'ERROR', // status
'OUT_OF_STOCK', // errorcode
'Some of the products included in the transaction are out of stock, therefore the transaction has not been processed.', // message
outOfStockProducts // details
);
}
// Create transaction with salesData
const transaction = await prisma.transactions.create({
data: {
userId: value.user_id,
total: total,
paid: false,
sales: {
create: salesData
}
},
select: {
id: true
}
});
// Everything went well
res.status(201).json({ status: 'CREATED', message: 'Successfully created transaction', id: transaction.id });
})
.catch((err) => {
if (err instanceof AbortError) {
res.status(err.http_status).json({
status: err.status,
errorcode: err.errorcode,
message: err.message,
details: err.details
});
} else {
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,
paidAt: value.paid ? new Date() : undefined
},
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 };