Moved away from SSR to regular Node API server.
This commit is contained in:
parent
9aea69c7be
commit
83d93aefc0
30 changed files with 939 additions and 1024 deletions
|
@ -1,705 +0,0 @@
|
|||
import { Router } from 'express';
|
||||
import prisma from '../database.js';
|
||||
import PDFDocument from 'pdfkit';
|
||||
import { join } from 'path';
|
||||
import { generateTodaysSummary } from '../services/mantisSummarizer.js';
|
||||
import { FieldType } from '@prisma/client';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname.replace(/\/$/, '');
|
||||
|
||||
// Helper function for consistent error handling
|
||||
const handlePrismaError = (res, err, context) =>
|
||||
{
|
||||
console.error(`Error ${context}:`, err.message);
|
||||
// Basic error handling, can be expanded (e.g., check for Prisma-specific error codes)
|
||||
if (err.code === 'P2025')
|
||||
{ // Prisma code for record not found
|
||||
return res.status(404).json({ error: `${context}: Record not found` });
|
||||
}
|
||||
res.status(500).json({ error: `Failed to ${context}: ${err.message}` });
|
||||
};
|
||||
|
||||
// --- Forms API --- //
|
||||
|
||||
// GET /api/forms - List all forms
|
||||
router.get('/forms', async(req, res) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const forms = await prisma.form.findMany({
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: { // Select only necessary fields
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
createdAt: true,
|
||||
}
|
||||
});
|
||||
res.json(forms);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, 'fetch forms');
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/forms - Create a new form
|
||||
router.post('/forms', async(req, res) =>
|
||||
{
|
||||
const { title, description, categories } = req.body;
|
||||
|
||||
if (!title)
|
||||
{
|
||||
return res.status(400).json({ error: 'Form title is required' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const newForm = await prisma.form.create({
|
||||
data: {
|
||||
title,
|
||||
description,
|
||||
categories: {
|
||||
create: categories?.map((category, catIndex) => ({
|
||||
name: category.name,
|
||||
sortOrder: catIndex,
|
||||
fields: {
|
||||
create: category.fields?.map((field, fieldIndex) =>
|
||||
{
|
||||
// Validate field type against Prisma Enum
|
||||
if (!Object.values(FieldType).includes(field.type))
|
||||
{
|
||||
throw new Error(`Invalid field type: ${field.type}`);
|
||||
}
|
||||
if (!field.label)
|
||||
{
|
||||
throw new Error('Field label is required');
|
||||
}
|
||||
return {
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
description: field.description || null,
|
||||
sortOrder: fieldIndex,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
})) || [],
|
||||
},
|
||||
},
|
||||
select: { // Return basic form info
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
}
|
||||
});
|
||||
res.status(201).json(newForm);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, 'create form');
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/forms/:id - Get a specific form with its structure
|
||||
router.get('/forms/:id', async(req, res) =>
|
||||
{
|
||||
const { id } = req.params;
|
||||
const formId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(formId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid form ID' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const form = await prisma.form.findUnique({
|
||||
where: { id: formId },
|
||||
include: {
|
||||
categories: {
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
include: {
|
||||
fields: {
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form)
|
||||
{
|
||||
return res.status(404).json({ error: 'Form not found' });
|
||||
}
|
||||
res.json(form);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `fetch form ${formId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/forms/:id - Delete a specific form and all related data
|
||||
router.delete('/forms/:id', async(req, res) =>
|
||||
{
|
||||
const { id } = req.params;
|
||||
const formId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(formId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid form ID' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Prisma automatically handles cascading deletes based on schema relations (onDelete: Cascade)
|
||||
const deletedForm = await prisma.form.delete({
|
||||
where: { id: formId },
|
||||
});
|
||||
res.status(200).json({ message: `Form ${formId} and all related data deleted successfully.` });
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `delete form ${formId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/forms/:id - Update an existing form
|
||||
router.put('/forms/:id', async(req, res) =>
|
||||
{
|
||||
const { id } = req.params;
|
||||
const formId = parseInt(id, 10);
|
||||
const { title, description, categories } = req.body;
|
||||
|
||||
if (isNaN(formId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid form ID' });
|
||||
}
|
||||
if (!title)
|
||||
{
|
||||
return res.status(400).json({ error: 'Form title is required' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Use a transaction to ensure atomicity: delete old structure, update form, create new structure
|
||||
const result = await prisma.$transaction(async(tx) =>
|
||||
{
|
||||
// 1. Check if form exists (optional, delete/update will fail if not found anyway)
|
||||
const existingForm = await tx.form.findUnique({ where: { id: formId } });
|
||||
if (!existingForm)
|
||||
{
|
||||
throw { code: 'P2025' }; // Simulate Prisma not found error
|
||||
}
|
||||
|
||||
// 2. Delete existing categories (fields and response values cascade)
|
||||
await tx.category.deleteMany({ where: { formId: formId } });
|
||||
|
||||
// 3. Update form details and recreate categories/fields in one go
|
||||
const updatedForm = await tx.form.update({
|
||||
where: { id: formId },
|
||||
data: {
|
||||
title,
|
||||
description,
|
||||
categories: {
|
||||
create: categories?.map((category, catIndex) => ({
|
||||
name: category.name,
|
||||
sortOrder: catIndex,
|
||||
fields: {
|
||||
create: category.fields?.map((field, fieldIndex) =>
|
||||
{
|
||||
if (!Object.values(FieldType).includes(field.type))
|
||||
{
|
||||
throw new Error(`Invalid field type: ${field.type}`);
|
||||
}
|
||||
if (!field.label)
|
||||
{
|
||||
throw new Error('Field label is required');
|
||||
}
|
||||
return {
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
description: field.description || null,
|
||||
sortOrder: fieldIndex,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
})) || [],
|
||||
},
|
||||
},
|
||||
select: { // Return basic form info
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
}
|
||||
});
|
||||
return updatedForm;
|
||||
});
|
||||
|
||||
res.status(200).json(result);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `update form ${formId}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- Responses API --- //
|
||||
|
||||
// POST /api/forms/:id/responses - Submit a response for a form
|
||||
router.post('/forms/:id/responses', async(req, res) =>
|
||||
{
|
||||
const { id } = req.params;
|
||||
const formId = parseInt(id, 10);
|
||||
const { values } = req.body; // values is expected to be { fieldId: value, ... }
|
||||
|
||||
if (isNaN(formId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid form ID' });
|
||||
}
|
||||
if (!values || typeof values !== 'object' || Object.keys(values).length === 0)
|
||||
{
|
||||
return res.status(400).json({ error: 'Response values are required' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Use transaction to ensure response and values are created together
|
||||
const result = await prisma.$transaction(async(tx) =>
|
||||
{
|
||||
// 1. Verify form exists
|
||||
const form = await tx.form.findUnique({ where: { id: formId }, select: { id: true } });
|
||||
if (!form)
|
||||
{
|
||||
throw { code: 'P2025' }; // Simulate Prisma not found error
|
||||
}
|
||||
|
||||
// 2. Create the response record
|
||||
const newResponse = await tx.response.create({
|
||||
data: {
|
||||
formId: formId,
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
|
||||
// 3. Prepare response values data
|
||||
const responseValuesData = [];
|
||||
const fieldIds = Object.keys(values).map(k => parseInt(k, 10));
|
||||
|
||||
// Optional: Verify all field IDs belong to the form (more robust)
|
||||
const validFields = await tx.field.findMany({
|
||||
where: {
|
||||
id: { 'in': fieldIds },
|
||||
category: { formId: formId }
|
||||
},
|
||||
select: { id: true }
|
||||
});
|
||||
const validFieldIds = new Set(validFields.map(f => f.id));
|
||||
|
||||
for (const fieldIdStr in values)
|
||||
{
|
||||
const fieldId = parseInt(fieldIdStr, 10);
|
||||
if (validFieldIds.has(fieldId))
|
||||
{
|
||||
const value = values[fieldIdStr];
|
||||
responseValuesData.push({
|
||||
responseId: newResponse.id,
|
||||
fieldId: fieldId,
|
||||
value: (value === null || typeof value === 'undefined') ? null : String(value),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn(`Attempted to submit value for field ${fieldId} not belonging to form ${formId}`);
|
||||
// Decide whether to throw an error or just skip invalid fields
|
||||
// throw new Error(`Field ${fieldId} does not belong to form ${formId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Create all response values
|
||||
if (responseValuesData.length > 0)
|
||||
{
|
||||
await tx.responseValue.createMany({
|
||||
data: responseValuesData,
|
||||
});
|
||||
}
|
||||
|
||||
return { responseId: newResponse.id };
|
||||
});
|
||||
|
||||
res.status(201).json(result);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `submit response for form ${formId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/forms/:id/responses - Get all responses for a form
|
||||
router.get('/forms/:id/responses', async(req, res) =>
|
||||
{
|
||||
const { id } = req.params;
|
||||
const formId = parseInt(id, 10);
|
||||
|
||||
if (isNaN(formId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid form ID' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Check if form exists
|
||||
const formExists = await prisma.form.findUnique({ where: { id: formId }, select: { id: true } });
|
||||
if (!formExists)
|
||||
{
|
||||
return res.status(404).json({ error: 'Form not found' });
|
||||
}
|
||||
|
||||
// 2. Fetch responses with their values and related field info
|
||||
const responses = await prisma.response.findMany({
|
||||
where: { formId: formId },
|
||||
orderBy: { submittedAt: 'desc' },
|
||||
include: {
|
||||
responseValues: {
|
||||
include: {
|
||||
field: {
|
||||
select: { label: true, type: true, category: { select: { sortOrder: true } }, sortOrder: true } // Include sort orders
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Group data similar to the old structure for frontend compatibility
|
||||
const groupedResponses = responses.map(response => ({
|
||||
id: response.id,
|
||||
submittedAt: response.submittedAt,
|
||||
values: response.responseValues
|
||||
.sort((a, b) =>
|
||||
{
|
||||
// Sort by category order, then field order
|
||||
const catSort = a.field.category.sortOrder - b.field.category.sortOrder;
|
||||
if (catSort !== 0) return catSort;
|
||||
return a.field.sortOrder - b.field.sortOrder;
|
||||
})
|
||||
.reduce((acc, rv) =>
|
||||
{
|
||||
acc[rv.fieldId] = {
|
||||
label: rv.field.label,
|
||||
type: rv.field.type,
|
||||
value: rv.value
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
}));
|
||||
|
||||
res.json(groupedResponses);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `fetch responses for form ${formId}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// GET /responses/:responseId/export/pdf - Export response as PDF
|
||||
router.get('/responses/:responseId/export/pdf', async(req, res) =>
|
||||
{
|
||||
const { responseId: responseIdStr } = req.params;
|
||||
const responseId = parseInt(responseIdStr, 10);
|
||||
|
||||
if (isNaN(responseId))
|
||||
{
|
||||
return res.status(400).json({ error: 'Invalid response ID' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Fetch the response, form title, form structure, and values in one go
|
||||
const responseData = await prisma.response.findUnique({
|
||||
where: { id: responseId },
|
||||
include: {
|
||||
form: {
|
||||
select: {
|
||||
title: true,
|
||||
categories: {
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
include: {
|
||||
fields: {
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
select: { id: true, label: true, type: true, description: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responseValues: {
|
||||
select: { fieldId: true, value: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!responseData)
|
||||
{
|
||||
return res.status(404).json({ error: 'Response not found' });
|
||||
}
|
||||
|
||||
const formTitle = responseData.form.title;
|
||||
const categories = responseData.form.categories;
|
||||
const responseValues = responseData.responseValues.reduce((acc, rv) =>
|
||||
{
|
||||
acc[rv.fieldId] = (rv.value === null || typeof rv.value === 'undefined') ? '' : String(rv.value);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// 4. Generate PDF using pdfkit (logic remains largely the same)
|
||||
const doc = new PDFDocument({ margin: 50, size: 'A4' });
|
||||
const fontsDir = join(__dirname, '../../public/fonts');
|
||||
|
||||
doc.registerFont('Roboto-Bold', join(fontsDir, 'Roboto-Bold.ttf'));
|
||||
doc.registerFont('Roboto-SemiBold', join(fontsDir, 'Roboto-SemiBold.ttf'));
|
||||
doc.registerFont('Roboto-Italics', join(fontsDir, 'Roboto-Italic.ttf'));
|
||||
doc.registerFont('Roboto-Regular', join(fontsDir, 'Roboto-Regular.ttf'));
|
||||
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', `inline; filename=response_${responseId}_${formTitle.replace(/[\s\\/]/g, '_') || 'form'}.pdf`);
|
||||
|
||||
doc.pipe(res);
|
||||
|
||||
// --- PDF Content (remains the same as before) ---
|
||||
doc.fontSize(18).font('Roboto-Bold').text(formTitle, { align: 'center' });
|
||||
doc.moveDown();
|
||||
|
||||
for (const category of categories)
|
||||
{
|
||||
if (category.name)
|
||||
{
|
||||
doc.fontSize(14).font('Roboto-Bold').text(category.name);
|
||||
doc.moveDown(0.5);
|
||||
}
|
||||
|
||||
for (const field of category.fields)
|
||||
{
|
||||
const value = responseValues[field.id] || '';
|
||||
doc.fontSize(12).font('Roboto-SemiBold').text(field.label + ':', { continued: false });
|
||||
if (field.description)
|
||||
{
|
||||
doc.fontSize(9).font('Roboto-Italics').text(field.description);
|
||||
}
|
||||
doc.moveDown(0.2);
|
||||
doc.fontSize(11).font('Roboto-Regular');
|
||||
if (field.type === 'textarea')
|
||||
{
|
||||
const textHeight = doc.heightOfString(value, { width: 500 });
|
||||
doc.rect(doc.x, doc.y, 500, Math.max(textHeight + 10, 30)).stroke();
|
||||
doc.text(value, doc.x + 5, doc.y + 5, { width: 490 });
|
||||
doc.y += Math.max(textHeight + 10, 30) + 10;
|
||||
}
|
||||
else if (field.type === 'date')
|
||||
{
|
||||
let formattedDate = '';
|
||||
if (value)
|
||||
{
|
||||
try
|
||||
{
|
||||
const dateObj = new Date(value + 'T00:00:00');
|
||||
if (!isNaN(dateObj.getTime()))
|
||||
{
|
||||
const day = String(dateObj.getDate()).padStart(2, '0');
|
||||
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
|
||||
const year = dateObj.getFullYear();
|
||||
formattedDate = `${day}/${month}/${year}`;
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedDate = value;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error('Error formatting date:', value, e);
|
||||
formattedDate = value;
|
||||
}
|
||||
}
|
||||
doc.text(formattedDate || ' ');
|
||||
doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke();
|
||||
doc.moveDown(1.5);
|
||||
}
|
||||
else if (field.type === 'boolean')
|
||||
{
|
||||
const displayValue = value === 'true' ? 'Yes' : (value === 'false' ? 'No' : ' ');
|
||||
doc.text(displayValue);
|
||||
doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke();
|
||||
doc.moveDown(1.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
doc.text(value || ' ');
|
||||
doc.lineCap('butt').moveTo(doc.x, doc.y).lineTo(doc.x + 500, doc.y).stroke();
|
||||
doc.moveDown(1.5);
|
||||
}
|
||||
}
|
||||
doc.moveDown(1);
|
||||
}
|
||||
doc.end();
|
||||
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error(`Error generating PDF for response ${responseId}:`, err.message);
|
||||
if (!res.headersSent)
|
||||
{
|
||||
// Use the helper function
|
||||
handlePrismaError(res, err, `generate PDF for response ${responseId}`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.error('Headers already sent, could not send JSON error for PDF generation failure.');
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- Mantis Summary API Route --- //
|
||||
|
||||
// GET /api/mantis-summary/today - Get today's summary specifically
|
||||
router.get('/mantis-summary/today', async(req, res) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0); // Set to start of day UTC for comparison
|
||||
|
||||
const todaySummary = await prisma.mantisSummary.findUnique({
|
||||
where: { summaryDate: today },
|
||||
select: { summaryDate: true, summaryText: true, generatedAt: true }
|
||||
});
|
||||
|
||||
if (todaySummary)
|
||||
{
|
||||
res.json(todaySummary);
|
||||
}
|
||||
else
|
||||
{
|
||||
res.status(404).json({ message: `No Mantis summary found for today (${today.toISOString().split('T')[0]}).` });
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
handlePrismaError(res, error, 'fetch today\'s Mantis summary');
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/mantis-summaries - Get ALL summaries from the DB, with pagination
|
||||
router.get('/mantis-summaries', async(req, res) =>
|
||||
{
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
const limit = parseInt(req.query.limit, 10) || 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
try
|
||||
{
|
||||
const [summaries, totalItems] = await prisma.$transaction([
|
||||
prisma.mantisSummary.findMany({
|
||||
orderBy: { summaryDate: 'desc' },
|
||||
take: limit,
|
||||
skip: skip,
|
||||
select: { id: true, summaryDate: true, summaryText: true, generatedAt: true }
|
||||
}),
|
||||
prisma.mantisSummary.count()
|
||||
]);
|
||||
|
||||
res.json({ summaries, total: totalItems });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
handlePrismaError(res, error, 'fetch paginated Mantis summaries');
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/mantis-summaries/generate - Trigger summary generation
|
||||
router.post('/mantis-summaries/generate', async(req, res) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Trigger generation asynchronously, don't wait for it
|
||||
generateTodaysSummary()
|
||||
.then(() =>
|
||||
{
|
||||
console.log('Summary generation process finished successfully (async).');
|
||||
})
|
||||
.catch(error =>
|
||||
{
|
||||
console.error('Background summary generation failed:', error);
|
||||
});
|
||||
|
||||
res.status(202).json({ message: 'Summary generation started.' });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
handlePrismaError(res, error, 'initiate Mantis summary generation');
|
||||
}
|
||||
});
|
||||
|
||||
// --- Settings API --- //
|
||||
|
||||
// GET /api/settings/:key - Get a specific setting value
|
||||
router.get('/settings/:key', async(req, res) =>
|
||||
{
|
||||
const { key } = req.params;
|
||||
try
|
||||
{
|
||||
const setting = await prisma.setting.findUnique({
|
||||
where: { key: key },
|
||||
select: { value: true }
|
||||
});
|
||||
|
||||
if (setting !== null)
|
||||
{
|
||||
res.json({ key, value: setting.value });
|
||||
}
|
||||
else
|
||||
{
|
||||
res.json({ key, value: '' }); // Return empty value if not found
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `fetch setting '${key}'`);
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/settings/:key - Update or create a specific setting
|
||||
router.put('/settings/:key', async(req, res) =>
|
||||
{
|
||||
const { key } = req.params;
|
||||
const { value } = req.body;
|
||||
|
||||
if (typeof value === 'undefined')
|
||||
{
|
||||
return res.status(400).json({ error: 'Setting value is required in the request body' });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const upsertedSetting = await prisma.setting.upsert({
|
||||
where: { key: key },
|
||||
update: { value: String(value) },
|
||||
create: { key: key, value: String(value) },
|
||||
select: { key: true, value: true } // Select to return the updated/created value
|
||||
});
|
||||
res.status(200).json(upsertedSetting);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
handlePrismaError(res, err, `update setting '${key}'`);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
Loading…
Add table
Add a link
Reference in a new issue