Compare commits

..

No commits in common. "b785dd8ca77a84e838adbe6916ff2ee245665f46" and "656ca2f74ae877616ca9e4b4e7a730acad48b72a" have entirely different histories.

9 changed files with 54 additions and 116 deletions

View File

@ -70,21 +70,9 @@ export function parseIntRelation(data: string, relation_name: string = 'id') {
// This function is perfect. If data is not a valid number, return `undefined` // This function is perfect. If data is not a valid number, return `undefined`
// If it is a valid number return `{connect: {relation_name: yourNumber}}}` // If it is a valid number return `{connect: {relation_name: yourNumber}}}`
// This can be used by prisma to connect relations // This can be used by prisma to connect relations
// If the incoming data is null or empty, return a prisma disconnect object instead of a connect one
if (data === null || data === '') {
return JSON.parse(`{
"disconnect": true
}`);
}
return isNaN(parseInt(data)) ? undefined : JSON.parse(`{ return isNaN(parseInt(data)) ? undefined : JSON.parse(`{
"connect": { "connect": {
"${relation_name}": ${parseInt(data)} "${relation_name}": ${parseInt(data)}
} }
}`); }`);
} }
export function parseIntOrUndefined(data: string) {
return isNaN(parseInt(data)) ? undefined : parseInt(data);
}

View File

@ -21,8 +21,8 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="itemModifyModalStorageLocation" class="form-label">Select a storage location</label> <label for="itemModifyModalStorageLocation" class="form-label">Select a storage location</label>
<select class="form-select" id="itemModifyModalStorageLocation" name="storageLocation"> <select class="form-select" id="itemModifyModalStorageLocation" name="storageLocation" required>
<option value=""><i>Do not assign a storage location</i></option> <option value="undefined"><i>Do not assign a storage location</i></option>
<% it.storeLocs.forEach(function(locs){ %> <% it.storeLocs.forEach(function(locs){ %>
<option value="<%= locs.id %>"><%= locs.name %></option> <option value="<%= locs.id %>"><%= locs.name %></option>
<% }) %> <% }) %>
@ -46,8 +46,8 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="itemModifyModalCategory" class="form-label">Select a category</label> <label for="itemModifyModalCategory" class="form-label">Select a category</label>
<select class="form-select" id="itemModifyModalCategory" name="category"> <select class="form-select" id="itemModifyModalCategory" name="category" required>
<option value=""><i>Do not assign a category</i></option> <option value="undefined"><i>Do not assign a category</i></option>
<% it.categories.forEach(function(cat){ %> <% it.categories.forEach(function(cat){ %>
<option value="<%= cat.id %>"><%= cat.name %></option> <option value="<%= cat.id %>"><%= cat.name %></option>
<% }) %> <% }) %>
@ -61,20 +61,13 @@
<option value="normal" class="text-success">Normal</option> <option value="normal" class="text-success">Normal</option>
<option value="borrowed" class="text-info">Borrowed</option> <option value="borrowed" class="text-info">Borrowed</option>
<option value="stolen" class="text-danger">Stolen</option> <option value="stolen" class="text-danger">Stolen</option>
<option value="lost" class="text-warning">Lost</option> <option value="lost" class="text-warning"">Lost</option>
</select> </select>
<div id="storageLocationModalLocationText" class="form-text">You have to create a storage location beforehand.</div> <div id="storageLocationModalLocationText" class="form-text">You have to create a storage location beforehand.</div>
</div> </div>
<div class="mb-3">
<label for="itemModifyModalContact" class="form-label">Contact Info</label>
<select class="form-select" id="itemModifyModalContact" name="contactInfoId" onchange="handleSelector()">
<option value=""><i>Do not assign contact info</i></option>
<% it.contactInfo.forEach(function(address){ %>
<option value="<%= address.id %>"><%= address.street %> <%= address.houseNumber %>, <%= address.city %> <%= address.country %></option>
<% }) %>
</select>
</div>
<input type="text" id="itemModifyModalId" name="id" hidden /> <input type="text" id="itemModifyModalId" name="id" hidden />
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -9,19 +9,10 @@ import storageLocationRoute from './storageLocations.js';
import search_routes from './search/index.js'; import search_routes from './search/index.js';
// Router base is '/api/v1' // Router base is '/api/v1'
const Router = express.Router({ strict: false }); const Router = express.Router({ strict: false });
// All empty strings are null values.
Router.use('*', function (req, res, next) {
for (let key in req.body) {
if (req.body[key] === '') {
req.body[key] = null;
}
}
next();
});
Router.route('/items').get(itemRoute.get).post(itemRoute.post).patch(itemRoute.patch).delete(itemRoute.del); Router.route('/items').get(itemRoute.get).post(itemRoute.post).patch(itemRoute.patch).delete(itemRoute.del);
Router.route('/categories').get(categoryRoute.get).post(categoryRoute.post).patch(categoryRoute.patch).delete(categoryRoute.del); Router.route('/categories').get(categoryRoute.get).post(categoryRoute.post).patch(categoryRoute.patch).delete(categoryRoute.del);
// TODO: Migrate routes to lowercase. // TODO: Migrate routes to lowercase.

View File

@ -1,27 +1,21 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { prisma, __path, log } from '../../../index.js'; import { prisma, __path, log } from '../../../index.js';
import { itemStatus } from '@prisma/client'; //import { itemStatus } from '@prisma/client';
import { parseIntRelation, parseIntOrUndefined } from '../../../assets/helper.js';
// Get item. // Get item.
function get(req: Request, res: Response) { function get(req: Request, res: Response) {
if (req.query.getAll === undefined) { if (req.query.getAll === undefined) {
// Check if required fields are present // Check if required fields are present.
if (!req.query.id) { if (!req.query.id) {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'One or more required fields are missing' }); res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'One or more required fields are missing' });
return; return;
} }
// Check if number is a valid integer
if (!Number.isInteger(parseInt(req.query.id.toString()))) {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'The id field must be an integer' });
return;
}
prisma.item prisma.item
.findUnique({ .findUnique({
where: { where: {
id: parseInt(req.query.id.toString()) id: parseInt(req.query.id.toString())
}, },
// Get contactInfo, category, storageLocation( storageUnit<contactInfo> ) from relations // Get contactInfo, category, storageLocation( storageUnit<contactInfo> ) from relations.
include: { include: {
contactInfo: true, contactInfo: true,
category: true, category: true,
@ -44,7 +38,7 @@ function get(req: Request, res: Response) {
} }
}) })
.catch((err) => { .catch((err) => {
log.db.error(err); console.error(err);
res.status(500).json({ errorcode: 'DB_ERROR', error: err }); res.status(500).json({ errorcode: 'DB_ERROR', error: err });
}); });
} else { } else {
@ -73,7 +67,7 @@ function get(req: Request, res: Response) {
} }
}) })
.catch((err) => { .catch((err) => {
log.db.error(err); console.error(err);
res.status(500).json({ errorcode: 'DB_ERROR', error: err }); res.status(500).json({ errorcode: 'DB_ERROR', error: err });
}); });
} }
@ -87,24 +81,19 @@ function post(req: Request, res: Response) {
return; return;
} }
// Check if status is valid.
if (req.body.status !== undefined && !Object.keys(itemStatus).includes(req.body.status)) {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: `Status is not valid, valid values are: ${Object.keys(itemStatus).join(', ')}` });
return;
}
prisma.item prisma.item
.create({ .create({
data: { data: {
SKU: req.body.sku, SKU: req.body.sku,
amount: parseIntOrUndefined(req.body.ammount), // FIXME: This is silently failing if NaN.. amount: req.body.ammount,
name: req.body.name, name: req.body.name,
comment: req.body.comment, comment: req.body.comment,
status: req.body.status, // Only enum(itemStatus) values are valid status: req.body.status, //itemStatus.normal,
// Relations // Relations
contactInfo: parseIntRelation(req.body.contactInfoId), contactInfoId: req.body.contactInfoId,
category: parseIntRelation(req.body.categoryId), categoryId: req.body.categoryId,
storageLocation: parseIntRelation(req.body.storageLocationId), storageLocationId: req.body.storageLocationId,
manufacturer: req.body.manufacturer, manufacturer: req.body.manufacturer,
@ -120,6 +109,7 @@ function post(req: Request, res: Response) {
} }
}) })
.then((data) => { .then((data) => {
// TODO: Check if id is returned correctly
res.status(201).json({ status: 'created', id: data.id }); res.status(201).json({ status: 'created', id: data.id });
}) })
.catch((err) => { .catch((err) => {
@ -127,12 +117,12 @@ function post(req: Request, res: Response) {
if (err.code === 'P2002') { if (err.code === 'P2002') {
// P2002 -> "Unique constraint failed on the {constraint}" // P2002 -> "Unique constraint failed on the {constraint}"
// https://www.prisma.io/docs/reference/api-reference/error-reference // https://www.prisma.io/docs/reference/api-reference/error-reference
res.status(409).json({ errorcode: 'EXISTING', error: 'Item already exists' }); res.status(409).json({ errorcode: 'EXISTING', error: 'storageLocation already exists' });
} else if (err.code == 'P2003') { } else if (err.code == 'P2003') {
// P2003 -> "Foreign key constraint failed on the field: {field_name}" // P2003 -> "Foreign key constraint failed on the field: {field_name}"
// https://www.prisma.io/docs/reference/api-reference/error-reference // https://www.prisma.io/docs/reference/api-reference/error-reference
// FIXME: Is this errormessage right? // FIXME: Is this errormessage right?
res.status(404).json({ errorcode: 'NOT_EXISTING', error: 'Specified item does not exist' }); res.status(404).json({ errorcode: 'NOT_EXISTING', error: 'specified storageUnitId does not exist' });
} else { } else {
log.db.error(err); log.db.error(err);
res.status(500).json({ errorcode: 'DB_ERROR', error: err }); res.status(500).json({ errorcode: 'DB_ERROR', error: err });
@ -142,51 +132,39 @@ function post(req: Request, res: Response) {
// Update storageLocation. -> Only existing contactInfo. // Update storageLocation. -> Only existing contactInfo.
async function patch(req: Request, res: Response) { async function patch(req: Request, res: Response) {
res.status(501).json({ errorcode: 'NOT_IMPLEMENTED', error: 'This endpoint is not yet implemented' });
/*
// Check if required fields are present. // Check if required fields are present.
if (!req.body.id) { if (!req.body.id || !req.body.name || !req.body.storageUnitId) {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'One or more required fields are missing' }); res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'One or more required fields are missing' });
return; return;
} }
// Check if number is a valid integer // Check if the storageLocation id exists. If not return 410 Gone.
if (!Number.isInteger(parseInt(req.body.id.toString()))) { try {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: 'The id field must be an integer' }); const result = await prisma.storageLocation.findUnique({
return; where: {
id: parseInt(req.body.id)
}
});
if (result === null) {
res.status(404).json({ errorcode: 'NOT_EXISTING', error: 'specified storageUnitId does not exist' });
return;
}
} catch (err) {
log.db.error(err);
res.status(500).json({ errorcode: 'DB_ERROR', error: err });
} }
// Check if status is valid. prisma.storageLocation
if (req.body.status !== undefined && !Object.keys(itemStatus).includes(req.body.status)) {
res.status(400).json({ errorcode: 'VALIDATION_ERROR', error: `Status is not valid, valid values are: ${Object.keys(itemStatus).join(', ')}` });
return;
}
prisma.item
.update({ .update({
where: { where: {
id: parseInt(req.body.id) id: parseInt(req.body.id)
}, },
data: { data: {
SKU: req.body.sku,
amount: parseIntOrUndefined(req.body.ammount), // FIXME: This is silently failing if NaN..
name: req.body.name, name: req.body.name,
comment: req.body.comment, storageUnitId: parseInt(req.body.storageUnitId) || undefined
status: req.body.status, // Only enum(itemStatus) values are valid
// Relations
contactInfo: parseIntRelation(req.body.contactInfoId),
category: parseIntRelation(req.body.categoryId),
storageLocation: parseIntRelation(req.body.storageLocationId),
manufacturer: req.body.manufacturer,
//contents: {
// connect: [{ id: 1 }, { id: 2 }, { id: 3 }]
//},
//baseItem: {
// connect: {
// id: req.body.baseitemId
// }
//},
createdBy: req.body.createdBy
} }
}) })
.then(() => { .then(() => {
@ -197,16 +175,17 @@ async function patch(req: Request, res: Response) {
if (err.code === 'P2002') { if (err.code === 'P2002') {
// P2002 -> "Unique constraint failed on the {constraint}" // P2002 -> "Unique constraint failed on the {constraint}"
// https://www.prisma.io/docs/reference/api-reference/error-reference // https://www.prisma.io/docs/reference/api-reference/error-reference
res.status(409).json({ errorcode: 'EXISTING', error: 'Item already exists', err: err }); res.status(409).json({ errorcode: 'EXISTING', error: 'storageLocation already exists' });
} else if (err.code == 'P2003') { } else if (err.code == 'P2003') {
// P2003 -> "Foreign key constraint failed on the field: {field_name}" // P2003 -> "Foreign key constraint failed on the field: {field_name}"
// https://www.prisma.io/docs/reference/api-reference/error-reference // https://www.prisma.io/docs/reference/api-reference/error-reference
res.status(404).json({ errorcode: 'NOT_EXISTING', error: 'Specified item does not exist' }); res.status(404).json({ error: 'specified storageUnitId does not exist' });
} else { } else {
log.db.error(err); log.db.error(err);
res.status(500).json({ errorcode: 'DB_ERROR', error: err }); res.status(500).json({ errorcode: 'DB_ERROR', error: err });
} }
}); });
*/
} }
// Delete item. // Delete item.

View File

@ -188,7 +188,7 @@ async function patch(req: Request, res: Response) {
name: req.body.name, name: req.body.name,
contactInfo: { contactInfo: {
connect: { connect: {
id: parseInt(req.body.locationId) // TODO: Rename to contactInfoId id: parseInt(req.body.locationId)
} }
} }
} }

View File

@ -27,10 +27,7 @@ async function get(req: Request, res: Response) {
.then((items) => { .then((items) => {
prisma.storageLocation.findMany({}).then((locations) => { prisma.storageLocation.findMany({}).then((locations) => {
prisma.itemCategory.findMany({}).then((categories) => { prisma.itemCategory.findMany({}).then((categories) => {
prisma.contactInfo.findMany({}).then((contactInfo) => { res.render(__path + '/src/frontend/items.eta.html', { items: items, currentPage: page, maxPages: pageSize, storeLocs: locations, categories: categories });
res.render(__path + '/src/frontend/items.eta.html', { items: items, currentPage: page, maxPages: pageSize, storeLocs: locations, categories: categories, contactInfo: contactInfo });
})
}); });
}); });
}) })

View File

@ -32,7 +32,6 @@ function getDataForEdit(id) {
const modal_itemCategory = document.getElementById('itemModifyModalCategory'); const modal_itemCategory = document.getElementById('itemModifyModalCategory');
const modal_itemStatus = document.getElementById('itemModifyModalStatus'); const modal_itemStatus = document.getElementById('itemModifyModalStatus');
const modal_itemid = document.getElementById('itemModifyModalId'); const modal_itemid = document.getElementById('itemModifyModalId');
const modal_userinfo = document.getElementById('itemModifyModalContact');
modal_itemName.value = result.name; modal_itemName.value = result.name;
modal_itemComment.value = result.comment; modal_itemComment.value = result.comment;
@ -43,7 +42,7 @@ function getDataForEdit(id) {
// Select the correct option in the dropdown // Select the correct option in the dropdown
const modal_itemCategoryOptions = modal_itemCategory.options; const modal_itemCategoryOptions = modal_itemCategory.options;
for (let i = 0; i < modal_itemCategoryOptions.length; i++) { for (let i = 0; i < modal_itemCategoryOptions.length; i++) {
if (modal_itemCategoryOptions[i].value == result.categoryId) { if (modal_itemCategoryOptions[i].value == result.category.id) {
modal_itemCategoryOptions[i].selected = true; modal_itemCategoryOptions[i].selected = true;
} }
} }
@ -51,7 +50,7 @@ function getDataForEdit(id) {
// Select the correct option in the dropdown // Select the correct option in the dropdown
const modal_itemStatusOptions = modal_itemStatus.options; const modal_itemStatusOptions = modal_itemStatus.options;
for (let i = 0; i < modal_itemStatusOptions.length; i++) { for (let i = 0; i < modal_itemStatusOptions.length; i++) {
if (modal_itemStatusOptions[i].value == result.statusId) { if (modal_itemStatusOptions[i].value == result.status.id) {
modal_itemStatusOptions[i].selected = true; modal_itemStatusOptions[i].selected = true;
} }
} }
@ -59,20 +58,11 @@ function getDataForEdit(id) {
// Select the correct option in the dropdown // Select the correct option in the dropdown
const modal_itemStorageLocationOptions = modal_itemStorageLocation.options; const modal_itemStorageLocationOptions = modal_itemStorageLocation.options;
for (let i = 0; i < modal_itemStorageLocationOptions.length; i++) { for (let i = 0; i < modal_itemStorageLocationOptions.length; i++) {
if (modal_itemStorageLocationOptions[i].value == result.storageLocationId) { if (modal_itemStorageLocationOptions[i].value == result.storageLocation.id) {
modal_itemStorageLocationOptions[i].selected = true; modal_itemStorageLocationOptions[i].selected = true;
} }
} }
modal_userinfo.selectedIndex = 0;
// Select the correct option in the dropdown
const modal_userInfoOptions = modal_userinfo.options;
for (let i = 0; i < modal_userInfoOptions.length; i++) {
if (modal_userInfoOptions[i].value == result.contactInfoId) {
modal_userInfoOptions[i].selected = true;
}
}
modal_itemid.value = result.id; modal_itemid.value = result.id;
}, },
error: function (data) { error: function (data) {

View File

@ -45,7 +45,7 @@ function primeEdit() {
document.getElementById('storageUnitModalLocationSelectText').innerText= "While editing you can only select already existing locations. Use the settings to create new ones."; document.getElementById('storageUnitModalLocationSelectText').innerText= "While editing you can only select already existing locations. Use the settings to create new ones.";
document.getElementById('storageUnitModalLabel').innerText = "Edit a storage unit"; document.getElementById('storageUnitModalLabel').innerText = "Edit a storage unit";
document.getElementById('storageLocationModalTitle').innerText = "Edit a storage location" document.getElementById('storageLocationModalTitle').innerText = "Edit a storage location"
document.getElementById('storageUnitModalLocationSelect').selectedIndex = 1 document.getElementById('storageUnitModalLocationSelect').value = 1
handleSelector() handleSelector()
form.setAttribute('method', 'PATCH'); form.setAttribute('method', 'PATCH');
form2.setAttribute('method', 'PATCH'); form2.setAttribute('method', 'PATCH');
@ -121,7 +121,7 @@ function getDataForEditLoc(id) {
// Select the correct location from the select based on the value of the option // Select the correct location from the select based on the value of the option
for(var i, j = 0; i = modal_locationUnitSel.options[j]; j++) { for(var i, j = 0; i = modal_locationUnitSel.options[j]; j++) {
if(i.value == result.storageUnitId) { if(i.value == result.storageUnit) {
console.log("Found it"); console.log("Found it");
modal_locationUnitSel.selectedIndex = j; modal_locationUnitSel.selectedIndex = j;
break; break;

View File

@ -105,7 +105,7 @@ function handleSearchSubmit(e) {
function handleHotKey(e) { function handleHotKey(e) {
// If c is pressed, focus on the search box // If c is pressed, focus on the search box
if(e.key == 'c' && e.altKey && e.ctrlKey) { if(e.key == 'c') {
// Show search_modal modal // Show search_modal modal
bootstrap.Modal.getOrCreateInstance($('#search_modal')).show() bootstrap.Modal.getOrCreateInstance($('#search_modal')).show()
document.getElementById('SearchBoxInput').focus(); document.getElementById('SearchBoxInput').focus();