building a foundation and breaking prisma

Co-authored-by: Spacelord <Spacelord09@users.noreply.github.com>
This commit is contained in:
Sören Oesterwind 2023-04-30 22:04:13 +02:00
commit 18a4393765
18 changed files with 2717 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
config.js
node_modules
.env
config.json
dist

20
.prettierrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"tabWidth": 8,
"useTabs": true,
"arrowParens": "always",
"bracketSameLine": true,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 200,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none"
}

1
README.md Normal file
View File

@ -0,0 +1 @@
# Assetflow

12
allowedStaticPaths.json Normal file
View File

@ -0,0 +1,12 @@
{
"allowedStaticFiles": [
"bootstrap-icons/font/bootstrap-icons.css",
"bootstrap/dist/css/bootstrap.min.css",
"bootstrap/dist/js/bootstrap.bundle.min.js",
"jquery/dist/jquery.min.js",
"darkreader/darkreader.js",
"bootstrap-icons/font/fonts/bootstrap-icons.woff2",
"bootstrap/dist/css/bootstrap.min.css.map"
],
"debugMode": false
}

52
eslintrc.json Normal file
View File

@ -0,0 +1,52 @@
{
"extends": ["eslint:recommended", "prettier"],
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2021
},
"rules": {
"arrow-spacing": ["warn", { "before": true, "after": true }],
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"handle-callback-err": "off",
"indent": ["error", "tab"],
"keyword-spacing": "error",
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"no-console": "off",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
"no-trailing-spaces": ["error"],
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"space-before-blocks": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

2221
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "assetflow",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"prestart": "npm run build",
"start": "node .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"assets",
"inventory",
"storage"
],
"author": "[Project-Name-Here]",
"license": "GPL-3.0",
"dependencies": {
"@prisma/client": "^4.13.0",
"bootstrap": "^5.3.0-alpha3",
"bootstrap-icons": "^1.10.5",
"eta": "^2.0.1",
"express": "^4.18.2",
"jquery": "^3.6.4",
"lodash": "^4.17.21",
"prisma": "^4.13.0",
"signale": "^1.4.0"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/lodash": "^4.14.194",
"@types/signale": "^1.4.4",
"eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0",
"typescript": "^5.0.4"
}
}

60
prisma/schema.prisma Normal file
View File

@ -0,0 +1,60 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
enum Category {
Light
Audio
Laptop
Adapter
Other
}
enum Status {
normal
borrowed
stolen
lost
}
model Item {
id Int @id @default(autoincrement())
SKU String @unique
Amount Int
Comment String?
name String
manufacturer String
category Category
status Status
StorageLocation StorageLocation @relation(fields: [storageLocationId], references: [id])
storageLocationId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model StorageLocation {
id Int @id @default(autoincrement())
name String
storageBuilding StorageBuilding @relation(fields: [storageBuildingId], references: [id])
storageBuildingId Int
Item Item[]
}
model StorageBuilding {
id Int @id @default(autoincrement())
name String
street String
houseNumber String
zipCode String
city String
country String
StorageLocation StorageLocation[]
}

View File

@ -0,0 +1,83 @@
import fs from 'node:fs';
import _ from 'lodash';
export type configObject = Record<any, any>
/**
* This class is responsible to save/edit config files.
*
* @export
* @class config
* @typedef {config}
*/
export default class config {
#configPath: string;
//global = {[key: string] : string}
global: configObject
/**
* Creates an instance of config.
*
* @constructor
* @param {string} configPath Path to config file.
* @param {object} configPreset Default config object with default values.
*/
constructor(configPath: string, configPreset: object) {
this.#configPath = configPath;
this.global = configPreset;
try {
// Read config
const data = fs.readFileSync(this.#configPath, 'utf8');
// Extend config with missing parameters from configPreset.
this.global = _.defaultsDeep(JSON.parse(data), this.global);
// Save config.
this.save_config();
} catch (err) {
console.error('Could not read config file at ' + this.#configPath + ' due to: ' + err);
}
}
/**
* Saves the jsonified config object to the config file.
*/
save_config() {
try {
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);
return;
}
console.log('Successfully written config file to ' + this.#configPath);
}
}
// BUG: If file does'nt exist -> fail.
// ToDo: Check for SyntaxError on fileread and ask if the user wants to continue -> overwrite everything. This behavior is currently standard.
/*
**** Example ****
const default_config = {
token: 'your-token-goes-here',
clientId: '',
devserverID: '',
devmode: true
};
import configHandler from './assets/config.js';
const config = new configHandler(__path + '/config.json', default_config);
console.log('Base Config:');
console.log(config.global);
console.log('Add some new key to config and call save_config.');
config.global.NewKey = 'ThisIsANewKey!'
config.save_config()
console.log('Complete Config:');
console.log(config.global);
*/

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AssetFlow - Info about item</title>
<meta name="description" content="A simple HTML5 Template for new projects." />
<meta name="author" content="[Project-Name-Here]" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<script src="/static/jquery/dist/jquery.min.js"></script>
<link href="/static/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<script src="/static/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="ui raised very padded text container segment">
<h2 class="ui header"><%= it.name %></h2>
<p><strong>Category:</strong> <%= it.category%></p>
<p><strong>Amount:</strong> <%= it.Amount %></p>
<p><strong>SKU:</strong> <%= it.SKU %></p>
<p><strong>Comment:</strong> <%= it.comment %></p>
</div>
</body>
</html>

0
src/frontend/style.css Normal file
View File

139
src/index.ts Normal file
View File

@ -0,0 +1,139 @@
import { Signale } from 'signale';
import ConfigHandler from './assets/configHandler';
import express, { Request, Response } from 'express';
import * as Eta from 'eta';
import { PrismaClient } from '@prisma/client';
import { Status, Category } from '@prisma/client';
import * as Path from 'path';
import * as fs from 'fs';
import routes from './routes/index.js'
// Get app directory.
const __path = process.argv[1];
const logger_settings = {
disabled: false,
logLevel: 'info',
scope: 'Core',
stream: process.stdout,
displayFilename: true
};
const coreLogger = new Signale(logger_settings);
const log = {
core: coreLogger,
db: coreLogger.scope('DB'),
web: coreLogger.scope('WEB')
};
// Create a new config instance.
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
});
const prisma = new PrismaClient({
datasources: {
db: {
url: config.global.db_connection_string
}
}
});
const app = express();
app.get('/dev/fillWithDemoData', (req, res) => {
// fill database with demo data
prisma.StorageBuilding.create({
data: {
name: "Test Storage Building",
street: "Test Street",
houseNumber: "1",
zipCode: "12345",
city: "Test City",
country: "Test Country",
}
}).then(() => {
prisma.StorageLocation.create({
data: {
name: "Test Storage Location",
StorageBuilding: {
connect: {
id: 1
}
}
}
}).then(() => {
prisma.item
.create({
data: {
SKU: 'ee189749',
Amount: 1,
name: 'Test Item',
manufacturer: 'Test Manufacturer',
category: Category.Other,
status: Status.normal,
storageLocation: {
connect: {
id: 1
}
},
}
})
.then(() => {
res.send('Demo data added');
})
.catch((err) => {
res.send('Error adding demo data: ' + err);
});
})
})
res.send('Demo data added (not)');
});
app.get('/:id', (req, res) => {
// retrieve data from database using id from url
prisma.item
.findFirst({
where: {
SKU: req.params.id
}
})
.then((item) => {
if (item) {
Eta.renderFile(__path + '/src/frontend/publicInfoPage.eta.html', item).then((html) => {
res.send(html);
});
} else {
res.send('Item not found');
}
});
});
// Load from allowsStaticPaths.json file
const allowedURLs: Array<string> = JSON.parse(fs.readFileSync("allowedStaticPaths.json", "utf8")).allowedStaticFiles;
const recordedURLs: Array<string> = [];
const debugMode: boolean = JSON.parse(fs.readFileSync("allowedStaticPaths.json", "utf8")).debugMode;
app.use('/static/*', function handleModuleFiles(req: Request, res: Response) {
if(debugMode) {
res.sendFile(Path.join(__dirname, 'node_modules', req.params[0]));
recordedURLs.push(req.params[0]);
log.web.debug(recordedURLs);
} else {
if (allowedURLs.indexOf(req.params[0]) > -1) {
res.sendFile(Path.join(__dirname, 'node_modules', req.params[0]));
} else {
log.web.warn('Attempt to access restricted asset file ' + req.params[0]);
res.status(403).json({ status: 'error', reason: 'Access to restricted asset file denied' });
}
}
// console.log(recordedURLs)
});
routes(app);
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}`);
});

9
src/routes/api/index.ts Normal file
View File

@ -0,0 +1,9 @@
import express from 'express';
const Router = express.Router();
import testRoute from './test.js';
Router.use("/api/test", testRoute)
export default Router;

4
src/routes/api/test.ts Normal file
View File

@ -0,0 +1,4 @@
import express, { Request, Response } from 'express';
export default (req: Request, res: Response) => {
res.status(200).send("Test Successful!");
};

View File

@ -0,0 +1,8 @@
import express from 'express';
const Router = express.Router();
import testRoute from './test.js';
Router.use("/test", testRoute)
export default Router;

View File

@ -0,0 +1,4 @@
import express, { Request, Response } from 'express';
export default (req: Request, res: Response) => {
res.status(200).send("Test Successful!");
};

8
src/routes/index.ts Normal file
View File

@ -0,0 +1,8 @@
import { Express } from 'express';
import frontend_routes from './frontend/index.js';
import api_routes from './frontend/index.js';
export default (app: Express) => {
app.use('/', frontend_routes);
app.use('/api', api_routes);
};

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"sourceRoot": "src",
"outDir": "dist",
"baseUrl": ".",
"skipLibCheck": true,
"paths": {
"*": [
"node_modules/*"
]
}
},
"include": [
"src/**/*"
],
}