Compare commits
17 Commits
db6df2fdc6
...
master
Author | SHA1 | Date | |
---|---|---|---|
db1d29f510 | |||
f3e306b084 | |||
c23b1b306c | |||
2371089f88 | |||
6fa2797903 | |||
af896a6688 | |||
347979bb10 | |||
ddfdfc3092 | |||
56cbebb36b | |||
e307ff97ac | |||
f52897fd4d | |||
b0b47e04f8 | |||
660c9c092e | |||
6b092b34b3 | |||
421085a8d5 | |||
ea80b4bf2b | |||
94186a3a18 |
139
package-lock.json
generated
139
package-lock.json
generated
@ -21,8 +21,11 @@
|
||||
"eta": "^2.0.1",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"express-session": "^1.17.3",
|
||||
"jquery": "^3.6.4",
|
||||
"lodash": "^4.17.21",
|
||||
"passport": "^0.6.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"signale": "^1.4.0",
|
||||
"tsparticles-confetti": "^2.9.3"
|
||||
},
|
||||
@ -30,7 +33,10 @@
|
||||
"@loancrate/prisma-schema-parser": "^2.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-fileupload": "^1.4.1",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/passport-local": "^1.0.35",
|
||||
"@types/signale": "^1.4.4",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
@ -832,6 +838,15 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-session": {
|
||||
"version": "1.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz",
|
||||
"integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.194",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz",
|
||||
@ -868,6 +883,36 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/passport": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.12.tgz",
|
||||
"integrity": "sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-local": {
|
||||
"version": "1.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz",
|
||||
"integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*",
|
||||
"@types/passport-strategy": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport-strategy": {
|
||||
"version": "0.2.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
|
||||
"integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/passport": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||
@ -2337,6 +2382,32 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
@ -4211,6 +4282,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@ -4422,6 +4501,42 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/passport": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
|
||||
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
|
||||
"dependencies": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1",
|
||||
"utils-merge": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jaredhanson"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-local": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
|
||||
"integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
|
||||
"dependencies": {
|
||||
"passport-strategy": "1.x.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/passport-strategy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@ -4469,6 +4584,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pause": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
@ -5116,6 +5236,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@ -6369,6 +6497,17 @@
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
@ -23,14 +23,17 @@
|
||||
"@sentry/tracing": "^7.52.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bootstrap": "^5.3.0-alpha3",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"bootstrap-icons": "^1.11.0",
|
||||
"bootstrap-table": "^1.22.1",
|
||||
"csv": "^6.2.11",
|
||||
"eta": "^2.0.1",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"express-session": "^1.17.3",
|
||||
"jquery": "^3.6.4",
|
||||
"lodash": "^4.17.21",
|
||||
"passport": "^0.6.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"signale": "^1.4.0",
|
||||
"tsparticles-confetti": "^2.9.3"
|
||||
},
|
||||
@ -38,7 +41,10 @@
|
||||
"@loancrate/prisma-schema-parser": "^2.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-fileupload": "^1.4.1",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/passport-local": "^1.0.35",
|
||||
"@types/signale": "^1.4.4",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs';
|
||||
import _ from 'lodash';
|
||||
import { randomUUID, randomBytes } from 'crypto';
|
||||
|
||||
export type configObject = Record<any, any>;
|
||||
|
||||
@ -14,6 +15,7 @@ export default class config {
|
||||
#configPath: string;
|
||||
//global = {[key: string] : string}
|
||||
global: configObject;
|
||||
replaceSecrets: boolean;
|
||||
|
||||
/**
|
||||
* Creates an instance of config.
|
||||
@ -22,9 +24,10 @@ export default class config {
|
||||
* @param {string} configPath Path to config file.
|
||||
* @param {object} configPreset Default config object with default values.
|
||||
*/
|
||||
constructor(configPath: string, configPreset: object) {
|
||||
constructor(configPath: string, replaceSecrets: boolean, configPreset: object) {
|
||||
this.#configPath = configPath;
|
||||
this.global = configPreset;
|
||||
this.replaceSecrets = replaceSecrets;
|
||||
|
||||
try {
|
||||
// Read config
|
||||
@ -52,6 +55,12 @@ export default class config {
|
||||
*/
|
||||
save_config() {
|
||||
try {
|
||||
// If enabled replace tokens defines as "gen" with random token
|
||||
if (this.replaceSecrets) {
|
||||
// Replace tokens with value "gen"
|
||||
this.generate_secrets(this.global, 'gen')
|
||||
}
|
||||
|
||||
fs.writeFileSync(this.#configPath, JSON.stringify(this.global, null, 8));
|
||||
} catch (err) {
|
||||
console.error(`Could not write config file at ${this.#configPath} due to: ${err}`);
|
||||
@ -59,31 +68,73 @@ export default class config {
|
||||
}
|
||||
console.log(`Successfully written config file to ${this.#configPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces each item matching the value of placeholder with a random UUID.
|
||||
* Thanks to https://stackoverflow.com/questions/8085004/iterate-through-nested-javascript-objects
|
||||
* @param {configObject} obj
|
||||
*/
|
||||
generate_secrets(obj: configObject, placeholder: string) {
|
||||
const stack = [obj];
|
||||
while (stack?.length > 0) {
|
||||
const currentObj = stack.pop();
|
||||
Object.keys(currentObj).forEach((key) => {
|
||||
|
||||
if (currentObj[key] === placeholder) {
|
||||
console.log('Generating secret: ' + key);
|
||||
currentObj[key] = randomBytes(48).toString('base64').replace(/\W/g, '');
|
||||
}
|
||||
|
||||
if (typeof currentObj[key] === 'object' && currentObj[key] !== null) {
|
||||
stack.push(currentObj[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
**** Example ****
|
||||
|
||||
import configHandler from './assets/configHandler.js';
|
||||
import ConfigHandlerNG from './assets/configHandlerNG.js';
|
||||
|
||||
// Create a new config instance.
|
||||
export const config = new ConfigHandler(__path + '/config.json', {
|
||||
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||
http_listen_address: '127.0.0.1',
|
||||
http_port: 3000,
|
||||
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
|
||||
debug: false
|
||||
});
|
||||
export const config = new ConfigHandler(__path + '/config.json', true, {
|
||||
test1: 't1',
|
||||
test2: 't2',
|
||||
test3: 'gen',
|
||||
test4: 't4',
|
||||
test5: 'gen',
|
||||
testObj: {
|
||||
local: {
|
||||
active: true,
|
||||
users: {
|
||||
user1: 'gen',
|
||||
user2: 'gen',
|
||||
user3: 'gen',
|
||||
user4: 'gen',
|
||||
|
||||
}
|
||||
},
|
||||
oidc: {
|
||||
active: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Base Config:');
|
||||
console.log(config.global);
|
||||
|
||||
console.log('Add some new key to config and call save_config.');
|
||||
console.log('Add some new key to config and call save_config().');
|
||||
config.global.NewKey = 'ThisIsANewKey!'
|
||||
config.save_config()
|
||||
|
||||
console.log('This will add a new key with value gen, but gen gets replaced with a random UUID when save_config() is called.');
|
||||
config.global.someSecret = 'gen'
|
||||
config.save_config() // global.someSecret is getting replaced with some random UUID since it was set to 'gen'.
|
||||
|
||||
console.log('Complete Config:');
|
||||
console.log(config.global);
|
||||
*/
|
||||
|
@ -4,9 +4,33 @@
|
||||
<div class="background text-center">
|
||||
<div class="row align-items-start">
|
||||
<div class="col-9"></div>
|
||||
<div class="col-3 sidePanel ps-4 text-black">
|
||||
<div class="col-3 sidePanel ps-4 pe-4 text-black">
|
||||
<h1>Log into AssetFlow</h1>
|
||||
<div class="alert alert-danger" role="alert" id="passwordAlarm">
|
||||
User does not exist or password is incorrect.
|
||||
</div>
|
||||
|
||||
<form action="/auth/login" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="userName" class="form-label">Username</label>
|
||||
<input name="username" type="text" class="form-control" id="userName" aria-describedby="userNameHelp" />
|
||||
<!-- <div id="userNameHelp" class="form-text">We'll never share your email with anyone else.</div> -->
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="userPassword" class="form-label">Password</label>
|
||||
<input name="password" type="password" class="form-control" id="userPassword" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// if url parameter ?failed is set, show the password alarm
|
||||
if (window.location.search.includes("failed")) {
|
||||
document.getElementById("passwordAlarm").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("passwordAlarm").style.display = "none";
|
||||
}
|
||||
</script>
|
||||
<%~ E.includeFile("../partials/foot.eta.html") %>
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@
|
||||
<th scope="col" data-field="name" data-sortable="true">Name</th>
|
||||
<th scope="col" data-field="comment" data-sortable="true" data-width="80">Comment</th>
|
||||
<th scope="col" data-field="status" data-sortable="true">Status</th>
|
||||
<th scope="col" data-field="actions" data-searchable="false">Actions</th>
|
||||
<th scope="col" data-field="actions" data-sortable="false" data-searchable="false" data-width="160">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<% if(it.items.length == 0) { %>
|
||||
|
@ -40,31 +40,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Table with all categories -->
|
||||
<table class="table align-middle">
|
||||
<table class="table align-middle" id="itemList" data-sortable="true" data-search-highlight="true" data-pagination="true" data-page-size="25" data-remember-order="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- <th scope="col">#</th> -->
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Action</th>
|
||||
<th scope="col" data-field="name" data-sortable="true" data-width="300">Name</th>
|
||||
<th scope="col" data-field="description" data-sortable="true">Description</th>
|
||||
<th scope="col" data-field="actions" data-sortable="false" data-searchable="false" data-width="160">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% it.items.forEach(function(user){ %>
|
||||
<tr id="listEntry-<%= user.id %>">
|
||||
<td scope="row" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="ID: <%= user.id %>"><%= user.name %></td>
|
||||
<td><%= user.description %></td>
|
||||
<td>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#editCategoryModal" onclick="primeEdit(); getDataForEdit('<%= user.name %>')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="preFillDeleteModalNxt('<%= user.name %>','categories','Category', 'name')" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<% }) %>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<script src="/js/editCategory.js"></script>
|
||||
|
64
src/index.ts
64
src/index.ts
@ -5,6 +5,9 @@ import fileUpload from 'express-fileupload';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import * as eta from 'eta';
|
||||
import bodyParser from 'body-parser';
|
||||
import session from 'express-session';
|
||||
import passport from 'passport';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Sentry
|
||||
import * as Sentry from '@sentry/node';
|
||||
@ -28,18 +31,40 @@ export const log = {
|
||||
core: coreLogger,
|
||||
db: coreLogger.scope('DB'),
|
||||
web: coreLogger.scope('WEB'),
|
||||
auth: coreLogger.scope('AUTH'),
|
||||
helper: coreLogger.scope('HELPER')
|
||||
};
|
||||
|
||||
// Create a new config instance.
|
||||
export const config = new ConfigHandler(__path + '/config.json', {
|
||||
export const config = new ConfigHandler(__path + '/config.json', true, {
|
||||
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||
http_listen_address: '127.0.0.1',
|
||||
http_port: 3000,
|
||||
sentry_dsn: 'https://ID@sentry.example.com/PROJECTID',
|
||||
debug: false
|
||||
debug: false,
|
||||
auth: {
|
||||
cookie_secret: 'gen',
|
||||
cookie_secure: true,
|
||||
local: {
|
||||
active: true,
|
||||
users: {}
|
||||
},
|
||||
oidc: {
|
||||
active: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no local User exists, create the default with a generated password
|
||||
if (_.isEqual(config.global.auth.local.users, {})) {
|
||||
config.global.auth.local.users = {
|
||||
'flowAdmin': 'gen',
|
||||
};
|
||||
config.save_config();
|
||||
}
|
||||
|
||||
|
||||
// TODO: Add errorhandling with some sort of message.
|
||||
export const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
@ -67,16 +92,16 @@ Sentry.init({
|
||||
environment: config.global.debug ? 'development' : 'production'
|
||||
});
|
||||
|
||||
app.locals.versionRevLong = require('child_process')
|
||||
.execSync('git rev-parse HEAD')
|
||||
.toString().trim()
|
||||
app.locals.versionRev = require('child_process')
|
||||
.execSync('git rev-parse --short HEAD')
|
||||
.toString().trim()
|
||||
app.locals.versionRevLatest = require('child_process')
|
||||
.execSync('git ls-remote --refs -q')
|
||||
.toString().trim().split("\t")[0]
|
||||
log.core.info(`Running revision ${app.locals.versionRevLong} (${app.locals.versionRevLatest} latest)`);
|
||||
// TODO: Version check need to be rewritten.
|
||||
app.locals.versionRevLong = require('child_process').execSync('git rev-parse HEAD').toString().trim();
|
||||
app.locals.versionRev = require('child_process').execSync('git rev-parse --short HEAD').toString().trim();
|
||||
app.locals.versionRevLatest = require('child_process').execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
|
||||
|
||||
if (app.locals.versionRevLong === app.locals.versionRevLatest) {
|
||||
log.core.info(`Running Latest Version (${app.locals.versionRevLong})`);
|
||||
} else {
|
||||
log.core.info(`Running Version: ${app.locals.versionRevLong} (Latest: ${app.locals.versionRevLatest})`);
|
||||
}
|
||||
|
||||
// RequestHandler creates a separate execution context using domains, so that every
|
||||
// transaction/span/breadcrumb is attached to its own Hub instance
|
||||
@ -93,14 +118,23 @@ app.use(bodyParser.urlencoded({ extended: false }));
|
||||
// Using bodyParser to parse JSON bodies into JS objects
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Session store
|
||||
// TODO: Move secret to config -> Autogenerate.
|
||||
app.use(
|
||||
session({
|
||||
secret: config.global.auth.cookie_secret,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: config.global.auth.cookie_secure }
|
||||
})
|
||||
);
|
||||
app.use(passport.authenticate('session'));
|
||||
|
||||
app.use(fileUpload());
|
||||
app.use(express.static(__path + '/static'));
|
||||
|
||||
app.use(routes);
|
||||
|
||||
// The error handler must be before any other error middleware and after all controllers
|
||||
app.use(Sentry.Handlers.errorHandler());
|
||||
|
||||
app.listen(config.global.http_port, config.global.http_listen_address, () => {
|
||||
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
|
||||
});
|
||||
|
21
src/middleware/auth.mw.ts
Normal file
21
src/middleware/auth.mw.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export function checkAuthentication(req: any, res: any, next: Function) {
|
||||
if (req.isAuthenticated()) {
|
||||
//req.isAuthenticated() will return true if user is logged in
|
||||
next();
|
||||
} else {
|
||||
res.redirect('/auth/login');
|
||||
}
|
||||
}
|
||||
|
||||
// const checkIsInRole = (...roles) => (req, res, next) => {
|
||||
// if (!req.user) {
|
||||
// return res.redirect('/login')
|
||||
// }
|
||||
|
||||
// const hasRole = roles.find(role => req.user.role === role)
|
||||
// if (!hasRole) {
|
||||
// return res.redirect('/login')
|
||||
// }
|
||||
|
||||
// return next()
|
||||
// }
|
@ -1,31 +1,97 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma, __path, log } from '../../../index.js';
|
||||
import { parseIntOrUndefined, parseDynamicSortBy } from '../../../assets/helper.js';
|
||||
|
||||
// Get category.
|
||||
function get(req: Request, res: Response) {
|
||||
// Get category
|
||||
async function get(req: Request, res: Response) {
|
||||
// Check if required fields are present.
|
||||
if (!req.query.name) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
if (req.query.sort === undefined) {
|
||||
req.query.sort = 'id';
|
||||
}
|
||||
if (req.query.order === undefined) {
|
||||
req.query.order = 'asc';
|
||||
}
|
||||
if (req.query.search === undefined) {
|
||||
req.query.search = '';
|
||||
}
|
||||
|
||||
prisma.itemCategory
|
||||
.findUnique({
|
||||
if (req.query.name) {
|
||||
prisma.itemCategory
|
||||
.findUnique({
|
||||
where: {
|
||||
name: req.query.name.toString()
|
||||
}
|
||||
})
|
||||
.then((item) => {
|
||||
if (item) {
|
||||
res.status(200).json(item);
|
||||
} else {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Category does not exist' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
});
|
||||
} else {
|
||||
// Get all items
|
||||
const itemCountNotFiltered = await prisma.itemCategory.count({});
|
||||
|
||||
// Get all items (filtered)
|
||||
const itemCountFiltered = await prisma.itemCategory.count({
|
||||
where: {
|
||||
name: req.query.name.toString()
|
||||
}
|
||||
})
|
||||
.then((item) => {
|
||||
if (item) {
|
||||
res.status(200).json(item);
|
||||
} else {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Category does not exist' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
},
|
||||
{
|
||||
description: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString())
|
||||
});
|
||||
|
||||
prisma.itemCategory
|
||||
.findMany({
|
||||
take: parseIntOrUndefined(req.query.limit),
|
||||
skip: parseIntOrUndefined(req.query.offset),
|
||||
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString()),
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
},
|
||||
{
|
||||
description: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
})
|
||||
.then((items) => {
|
||||
if (items) {
|
||||
res.status(200).json({ total: itemCountFiltered, totalNotFiltered: itemCountNotFiltered, items: items });
|
||||
} else {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Item does not exist' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create category.
|
||||
@ -56,6 +122,10 @@ function post(req: Request, res: Response) {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'Category already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
@ -110,6 +180,10 @@ async function patch(req: Request, res: Response) {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'Category already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
|
244
src/routes/api/v1/contactInfo.ts
Normal file
244
src/routes/api/v1/contactInfo.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma, __path, log } from '../../../index.js';
|
||||
import { parseIntOrUndefined, parseDynamicSortBy } from '../../../assets/helper.js';
|
||||
|
||||
// Get category
|
||||
async function get(req: Request, res: Response) {
|
||||
// Check if required fields are present.
|
||||
if (req.query.sort === undefined) {
|
||||
req.query.sort = 'id';
|
||||
}
|
||||
if (req.query.order === undefined) {
|
||||
req.query.order = 'asc';
|
||||
}
|
||||
if (req.query.search === undefined) {
|
||||
req.query.search = '';
|
||||
}
|
||||
|
||||
if (req.query.id) {
|
||||
prisma.contactInfo
|
||||
.findUnique({
|
||||
where: {
|
||||
id: parseInt(req.query.id.toString())
|
||||
}
|
||||
})
|
||||
.then((item) => {
|
||||
if (item) {
|
||||
res.status(200).json(item);
|
||||
} else {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'ContactInfo does not exist' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
});
|
||||
} else {
|
||||
// Get all items
|
||||
const itemCountNotFiltered = await prisma.contactInfo.count({});
|
||||
|
||||
// Get all items (filtered)
|
||||
const itemCountFiltered = await prisma.contactInfo.count({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
},
|
||||
{
|
||||
street: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString())
|
||||
});
|
||||
|
||||
prisma.contactInfo
|
||||
.findMany({
|
||||
take: parseIntOrUndefined(req.query.limit),
|
||||
skip: parseIntOrUndefined(req.query.offset),
|
||||
orderBy: parseDynamicSortBy(req.query.sort.toString(), req.query.order.toString()),
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
},
|
||||
{
|
||||
street: {
|
||||
// @ts-ignore
|
||||
contains: req.query.search.length > 0 ? req.query.search : ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
})
|
||||
.then((items) => {
|
||||
if (items) {
|
||||
res.status(200).json({ total: itemCountFiltered, totalNotFiltered: itemCountNotFiltered, items: items });
|
||||
} else {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'ContactInfo does not exist' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create category.
|
||||
function post(req: Request, res: Response) {
|
||||
// Check if required fields are present.
|
||||
if (!req.body.street || !req.body.houseNumber || !req.body.zipCode || !req.body.city || !req.body.country) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Save data.
|
||||
prisma.contactInfo
|
||||
.create({
|
||||
data: {
|
||||
name: req.body.name,
|
||||
lastName: req.body.lastName,
|
||||
street: req.body.street,
|
||||
houseNumber: req.body.houseNumber,
|
||||
zipCode: req.body.zipCode,
|
||||
city: req.body.city,
|
||||
country: req.body.country,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
res.status(201).json({ status: 'CREATED', message: 'Successfully created contactInfo', id: data.id });
|
||||
})
|
||||
.catch((err) => {
|
||||
// Check if an entry already exists.
|
||||
if (err.code === 'P2002') {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'ContactInfo already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update category.
|
||||
async function patch(req: Request, res: Response) {
|
||||
// Check if required fields are present.
|
||||
if (!req.body.id || !req.body.name) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the category id exists. If not return 410 Gone.
|
||||
try {
|
||||
const result = await prisma.contactInfo.findUnique({
|
||||
where: {
|
||||
id: parseInt(req.body.id)
|
||||
}
|
||||
});
|
||||
|
||||
if (result === null) {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Category does not exist' });
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
}
|
||||
|
||||
prisma.contactInfo
|
||||
.update({
|
||||
where: {
|
||||
id: parseInt(req.body.id)
|
||||
},
|
||||
data: {
|
||||
name: req.body.name,
|
||||
lastName: req.body.lastName,
|
||||
street: req.body.street,
|
||||
houseNumber: req.body.houseNumber,
|
||||
zipCode: req.body.zipCode,
|
||||
city: req.body.city,
|
||||
country: req.body.country,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
res.status(201).json({ status: 'UPDATED', message: 'Successfully updated category', id: data.id });
|
||||
})
|
||||
.catch((err) => {
|
||||
// Check if an entry already exists.
|
||||
if (err.code === 'P2002') {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'Category already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Delete category
|
||||
async function del(req: Request, res: Response) {
|
||||
// Check if required fields are present.
|
||||
if (!req.body.id) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Does the id exist? If not return 410 Gone.
|
||||
try {
|
||||
const result = await prisma.contactInfo.findUnique({
|
||||
where: {
|
||||
id: parseInt(req.body.id)
|
||||
}
|
||||
});
|
||||
|
||||
if (result === null) {
|
||||
res.status(410).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'ContactInfo does not exist' });
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
}
|
||||
|
||||
prisma.contactInfo
|
||||
.delete({
|
||||
where: {
|
||||
id: parseInt(req.body.id)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).json({ status: 'DELETED', message: 'Successfully deleted contactInfo' });
|
||||
})
|
||||
.catch((err) => {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
});
|
||||
}
|
||||
|
||||
export default { get, post, patch, del };
|
@ -1,4 +1,5 @@
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
|
||||
// Route imports
|
||||
import testRoute from './test.js';
|
||||
@ -6,6 +7,7 @@ import itemRoute from './items.js';
|
||||
import categoryRoute from './categories.js';
|
||||
import storageUnitRoute from './storageUnits.js';
|
||||
import storageLocationRoute from './storageLocations.js';
|
||||
import contactInfo from './contactInfo.js';
|
||||
import versionRoute from './version.js'
|
||||
|
||||
import search_routes from './search/index.js';
|
||||
@ -28,6 +30,8 @@ Router.route('/categories').get(categoryRoute.get).post(categoryRoute.post).patc
|
||||
// TODO: Migrate routes to lowercase.
|
||||
Router.route('/storageUnits').get(storageUnitRoute.get).post(storageUnitRoute.post).patch(storageUnitRoute.patch).delete(storageUnitRoute.del);
|
||||
Router.route('/storageLocations').get(storageLocationRoute.get).post(storageLocationRoute.post).patch(storageLocationRoute.patch).delete(storageLocationRoute.del);
|
||||
Router.route('/contactInfo').get(contactInfo.get).post(contactInfo.post).patch(contactInfo.patch).delete(contactInfo.del);
|
||||
|
||||
Router.route('/version').get(versionRoute.get);
|
||||
Router.use('/search', search_routes);
|
||||
|
||||
|
@ -183,6 +183,10 @@ function post(req: Request, res: Response) {
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
// FIXME: Is this errormessage right?
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Item does not exist' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
@ -255,6 +259,10 @@ async function patch(req: Request, res: Response) {
|
||||
// P2003 -> "Foreign key constraint failed on the field: {field_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'Item does not exist' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
|
@ -14,12 +14,7 @@ async function get(req: Request, res: Response) {
|
||||
req.query.search = '';
|
||||
}
|
||||
|
||||
if (req.query.getAll === undefined) {
|
||||
// Check if required fields are present.
|
||||
if (!req.query.id) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
}
|
||||
if (req.query.id) {
|
||||
prisma.storageLocation
|
||||
.findUnique({
|
||||
where: {
|
||||
@ -118,6 +113,10 @@ function post(req: Request, res: Response) {
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
// FIXME: Is this errormessage right?
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'storageUnitId does not exist' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
@ -177,6 +176,10 @@ async function patch(req: Request, res: Response) {
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
// FIXME: Is this errormessage right?
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'NOT_EXISTING', message: 'storageUnitId does not exist' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
|
@ -14,12 +14,7 @@ async function get(req: Request, res: Response) {
|
||||
if (req.query.search === undefined) {
|
||||
req.query.search = '';
|
||||
}
|
||||
if (req.query.getAll === undefined) {
|
||||
// Check if required fields are present.
|
||||
if (!req.query.id) {
|
||||
res.status(400).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more required fields are missing' });
|
||||
return;
|
||||
}
|
||||
if (req.query.id) {
|
||||
prisma.storageUnit
|
||||
.findUnique({
|
||||
where: {
|
||||
@ -127,6 +122,10 @@ function post(req: Request, res: Response) {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'storageUnit already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
@ -162,6 +161,10 @@ function post(req: Request, res: Response) {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'storageUnit already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
@ -238,6 +241,10 @@ async function patch(req: Request, res: Response) {
|
||||
// P2002 -> "Unique constraint failed on the {constraint}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(409).json({ status: 'ERROR', errorcode: 'EXISTING', message: 'storageUnit already exists' });
|
||||
} else if (err.code == 'P2000') {
|
||||
// P2000 -> "The provided value for the column is too long for the column's type. Column: {column_name}"
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
res.status(404).json({ status: 'ERROR', errorcode: 'VALIDATION_ERROR', message: 'One or more fields exceed the maximum length restriction' });
|
||||
} else {
|
||||
log.db.error(err);
|
||||
res.status(500).json({ status: 'ERROR', errorcode: 'DB_ERROR', error: err, message: 'An error occurred during the database operation' });
|
||||
|
90
src/routes/auth/index.ts
Normal file
90
src/routes/auth/index.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import passport from 'passport';
|
||||
import { Strategy as LocalStrategy } from 'passport-local';
|
||||
import express, { Request, Response } from 'express';
|
||||
import { prisma, __path, log, config, app } from '../../index.js';
|
||||
|
||||
// Middleware Imports
|
||||
import { checkAuthentication } from '../../middleware/auth.mw.js'
|
||||
|
||||
/* Configure password authentication strategy.
|
||||
*
|
||||
* The `LocalStrategy` authenticates users by verifying a username and password.
|
||||
* The strategy parses the username and password from the request and calls the
|
||||
* `verify` function.
|
||||
*
|
||||
* The `verify` function queries the database for the user record and verifies
|
||||
* the password by hashing the password supplied by the user and comparing it to
|
||||
* the hashed password stored in the database. If the comparison succeeds, the
|
||||
* user is authenticated; otherwise, not.
|
||||
*/
|
||||
passport.use(
|
||||
new LocalStrategy(function verify(username, password, cb) {
|
||||
//log.auth.debug('LocalStrategy:', username, password);
|
||||
|
||||
for (const [user, pass] of Object.entries(config.global.auth.local.users)) {
|
||||
//log.auth.debug('Loop(REQ):', username, password);
|
||||
//log.auth.debug('Loop(CFG):', user, pass);
|
||||
|
||||
if (user.toLowerCase() === username.toLowerCase() && pass === password) {
|
||||
log.auth.debug('LocalStrategy: success');
|
||||
return cb(null, { username: username }); // This is the user object.
|
||||
}
|
||||
}
|
||||
log.auth.debug('LocalStrategy: failed');
|
||||
return cb(null, false, { message: 'Incorrect username or password.' });
|
||||
})
|
||||
/*
|
||||
1. If the user not found in DB,
|
||||
done (null, false)
|
||||
|
||||
2. If the user found in DB, but password does not match,
|
||||
done (null, false)
|
||||
|
||||
3. If user found in DB and password match,
|
||||
done (null, {authenticated_user})
|
||||
*/
|
||||
);
|
||||
|
||||
/* Configure session management.
|
||||
*
|
||||
* When a login session is established, information about the user will be
|
||||
* stored in the session. This information is supplied by the `serializeUser`
|
||||
* function, which is yielding the user ID and username.
|
||||
*
|
||||
* As the user interacts with the app, subsequent requests will be authenticated
|
||||
* by verifying the session. The same user information that was serialized at
|
||||
* session establishment will be restored when the session is authenticated by
|
||||
* the `deserializeUser` function.
|
||||
*
|
||||
*/
|
||||
passport.serializeUser(function (user: any, cb) {
|
||||
process.nextTick(function () {
|
||||
// log.auth.debug('Called seriealizeUser');
|
||||
// log.auth.debug('user:', user);
|
||||
return cb(null, {
|
||||
username: user.username
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
passport.deserializeUser(function (user, cb) {
|
||||
process.nextTick(function () {
|
||||
// log.auth.debug('Called deseriealizeUser');
|
||||
return cb(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
// Route imports
|
||||
import testRoute from './test.js';
|
||||
import loginRoute from './login.js';
|
||||
//import logoutRoute from './login.js'
|
||||
|
||||
// Router base is '/auth'
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
Router.route('/login').get(loginRoute.get);
|
||||
Router.route('/login').post(passport.authenticate('local', { successRedirect: '/', failureRedirect: '/auth/login?failed' }));
|
||||
|
||||
Router.route('/test').get(checkAuthentication, testRoute.get);
|
||||
|
||||
export default Router;
|
@ -1,5 +1,7 @@
|
||||
import passport from 'passport';
|
||||
|
||||
import express, { Request, Response } from 'express';
|
||||
import { prisma, __path } from '../../../index.js';
|
||||
import { prisma, __path, log } from '../../index.js';
|
||||
|
||||
function get(req: Request, res: Response) {
|
||||
res.render(__path + '/src/frontend/auth/login.eta.html'); //, { items: items });
|
7
src/routes/auth/test.ts
Normal file
7
src/routes/auth/test.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
function get(req: Request, res: Response) {
|
||||
res.status(200).send('Auth Test Successful!');
|
||||
};
|
||||
|
||||
export default { get };
|
@ -7,7 +7,6 @@ import jsonImportRoute from './import/jsonImport.js';
|
||||
import categoryManager from './categoryManager.js';
|
||||
import storageManager from './storageManager.js';
|
||||
import startpageRoute from './startpage.js';
|
||||
import demoLoginRoute from './demo.js'
|
||||
|
||||
// Router base is '/manage'
|
||||
const Router = express.Router({ strict: false });
|
||||
@ -17,7 +16,6 @@ Router.route('/categories').get(categoryManager.get);
|
||||
Router.route('/storages').get(storageManager.get);
|
||||
Router.route('/import/csv').get(csvImportRoute.get).post(csvImportRoute.post);
|
||||
Router.route('/import/json').get(jsonImportRoute.get).post(jsonImportRoute.post);
|
||||
Router.route('/demo/login').get(demoLoginRoute.get);
|
||||
Router.route('/').get(startpageRoute.get);
|
||||
|
||||
export default Router;
|
||||
|
@ -1,16 +1,25 @@
|
||||
import express, { Express } from 'express';
|
||||
import { __path, prisma } from '../index.js';
|
||||
import * as Sentry from '@sentry/node';
|
||||
|
||||
// Middleware Imports
|
||||
import { checkAuthentication } from '../middleware/auth.mw.js'
|
||||
|
||||
// Route imports
|
||||
import frontend_routes from './frontend/index.js';
|
||||
import static_routes from './static/index.js';
|
||||
import api_routes from './api/index.js';
|
||||
import auth_routes from './auth/index.js';
|
||||
|
||||
const Router = express.Router({ strict: false });
|
||||
|
||||
Router.use('/static', static_routes);
|
||||
Router.use('/api', api_routes);
|
||||
Router.use('/', frontend_routes);
|
||||
Router.use('/api', checkAuthentication, api_routes);
|
||||
Router.use('/auth', auth_routes);
|
||||
Router.use('/', checkAuthentication, frontend_routes);
|
||||
|
||||
// The error handler must be before any other error middleware and after all controllers
|
||||
Router.use(Sentry.Handlers.errorHandler());
|
||||
|
||||
// Default route.
|
||||
Router.all('*', function (req, res) {
|
||||
@ -23,3 +32,4 @@ Router.all('*', function (req, res) {
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
||||
|
@ -6,6 +6,11 @@ body {
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
/* Give the logo a dark shadow to make it pop out */
|
||||
.headLogo {
|
||||
filter: drop-shadow(0 0 0.85rem rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
.headLogo {
|
||||
width: 40%;
|
||||
|
@ -3,6 +3,7 @@ function randomInRange(min, max) {
|
||||
}
|
||||
|
||||
function doTheConfetti() {
|
||||
// Create confetti
|
||||
confetti({
|
||||
angle: randomInRange(90, 110),
|
||||
spread: randomInRange(70, 120),
|
||||
|
@ -1,3 +1,5 @@
|
||||
const FLAG_supports_new_data_loader = true;
|
||||
|
||||
function getDataForEdit(name) {
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
@ -37,3 +39,42 @@ function primeEdit() {
|
||||
form.setAttribute('method', 'PATCH');
|
||||
return true;
|
||||
}
|
||||
|
||||
const itemList = $('#itemList');
|
||||
// itemList.empty();
|
||||
itemList.bootstrapTable({ url: '/api/v1/categories', search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
setTimeout(() => {
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
|
||||
function loadPageData() {
|
||||
itemList.bootstrapTable('refresh')
|
||||
setTimeout(() => {
|
||||
$(".tooltip").tooltip("hide");
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function dataResponseHandler(json) {
|
||||
// console.log(json)
|
||||
totalNotFiltered = json.totalNotFiltered;
|
||||
total = json.total;
|
||||
json = json.items;
|
||||
json.forEach((item) => {
|
||||
item.actions = `
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#editCategoryModal" onclick="primeEdit(); getDataForEdit('${item.name}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="preFillDeleteModalNxt('${item.name}','categories','Category','name')" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>`
|
||||
});
|
||||
///// --------------------------------- /////
|
||||
setTimeout(() => {
|
||||
activateTooltips();
|
||||
}, 200);
|
||||
return {"rows": json, total: total, totalNotFiltered: totalNotFiltered, totalRows: total};
|
||||
|
||||
}
|
||||
|
||||
loadPageData()
|
||||
|
@ -8,6 +8,16 @@ function primeCreateNew() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function triggerDuplicationDialog(sourceItemId) {
|
||||
// Clear the form
|
||||
$('.form-control').val('');
|
||||
const form = document.getElementById('ItemModalForm');
|
||||
document.getElementById('itemModifyModalLabel').innerText= "Duplicate an item";
|
||||
form.setAttribute('method', 'POST');
|
||||
getDataForEdit(sourceItemId);
|
||||
return true;
|
||||
}
|
||||
|
||||
function primeEdit() {
|
||||
const form = document.getElementById('ItemModalForm');
|
||||
document.getElementById('itemModifyModalLabel').innerText = 'Edit an item';
|
||||
|
@ -138,8 +138,8 @@ const itemList = $('#itemList');
|
||||
const itemListUnit = $('#itemListUnit');
|
||||
|
||||
// itemList.empty();
|
||||
itemListUnit.bootstrapTable({ url: '/api/v1/storageUnits?getAll=true', search: true, showRefresh: true, responseHandler: dataResponseHandlerUnit, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
itemList.bootstrapTable({ url: '/api/v1/storageLocations?getAll=true', search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
itemListUnit.bootstrapTable({ url: '/api/v1/storageUnits', search: true, showRefresh: true, responseHandler: dataResponseHandlerUnit, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
itemList.bootstrapTable({ url: '/api/v1/storageLocations', search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
setTimeout(() => {
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
@ -150,6 +150,7 @@ function loadPageData() {
|
||||
itemListUnit.bootstrapTable('refresh');
|
||||
|
||||
setTimeout(() => {
|
||||
$(".tooltip").tooltip("hide");
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -7,18 +7,15 @@ const FLAG_supports_new_data_loader = true;
|
||||
|
||||
// Inital thing
|
||||
const itemList = $('#itemList');
|
||||
// itemList.empty();
|
||||
itemList.bootstrapTable({url: "/api/v1/items", search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true, silentSort: false})
|
||||
setTimeout(() => {
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
itemList.bootstrapTable({ url: '/api/v1/items', search: true, showRefresh: true, responseHandler: dataResponseHandler, sidePagination: 'server', serverSort: true, silentSort: false });
|
||||
setTimeout(() => {
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
|
||||
function loadPageData() {
|
||||
const itemList = $('#itemList');
|
||||
// itemList.empty();
|
||||
itemList.bootstrapTable('refresh')
|
||||
|
||||
setTimeout(() => {
|
||||
$(".tooltip").tooltip("hide");
|
||||
activateTooltips();
|
||||
}, 1000);
|
||||
}
|
||||
@ -52,6 +49,9 @@ function dataResponseHandler(json) {
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="primeEdit(); getDataForEdit('${item.id}')">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#itemModifyModal" onclick="triggerDuplicationDialog('${item.id}')">
|
||||
<i class="bi bi-copy"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="preFillDeleteModalNxt('${item.id}','items','Item')" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>`
|
||||
|
@ -60,7 +60,7 @@ function handleSearchChange(e) {
|
||||
autocompleteBox.innerHTML = 'Start typing to search for commands <br> #SKU';
|
||||
return;
|
||||
}
|
||||
const baseURI = window.location.origin;
|
||||
const baseURI = window.location.origin; // move to new fancy route
|
||||
const url = baseURI + '/api/v1/search/sku?sku=' + searchedSKU;
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user