generated from corysanin/nodejs-web-template
146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import * as http from "http";
|
|
import crypto from 'crypto';
|
|
import express, { type Express } from 'express';
|
|
import bodyParser from 'body-parser';
|
|
import { type GrocyUser, type Grocy } from './Grocy.js';
|
|
|
|
interface WebConfig {
|
|
sessionSecret?: string;
|
|
port?: number;
|
|
secure?: boolean;
|
|
}
|
|
|
|
/**
|
|
* I still hate typescript.
|
|
*/
|
|
function notStupidParseInt(v: string | undefined): number {
|
|
return v === undefined ? NaN : parseInt(v);
|
|
}
|
|
|
|
class Web {
|
|
private _webserver: http.Server | null = null;
|
|
private app: Express | null = null;
|
|
private port: number;
|
|
// private options: WebConfig;
|
|
private grocy: Grocy;
|
|
|
|
constructor(options: WebConfig = {}, grocy: Grocy) {
|
|
// this.options = options;
|
|
this.port = notStupidParseInt(process.env['PORT']) || options['port'] as number || 8080;
|
|
this.grocy = grocy
|
|
}
|
|
|
|
initialize = async () => {
|
|
const app: Express = this.app = express();
|
|
|
|
app.set('trust proxy', 1);
|
|
app.set('view engine', 'ejs');
|
|
app.set('view options', { outputFunctionName: 'echo' });
|
|
app.use('/assets', express.static('assets', { maxAge: '30 days' }));
|
|
app.use(bodyParser.json());
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use((_req, res, next) => {
|
|
crypto.randomBytes(32, (err, randomBytes) => {
|
|
if (err) {
|
|
console.error(err);
|
|
next(err);
|
|
} else {
|
|
res.locals['cspNonce'] = randomBytes.toString("hex");
|
|
next();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.get('/healthcheck', (_, res) => {
|
|
res.send('Healthy');
|
|
});
|
|
|
|
app.get('/', (_, res) => {
|
|
res.render('app', {
|
|
page: {
|
|
title: 'Chore Chart',
|
|
titlesuffix: 'View Grocy Chores',
|
|
description: 'chore-chart displays chores from a Grocy instance.'
|
|
}
|
|
});
|
|
});
|
|
|
|
app.get('/api/chores', async (_, res) => {
|
|
const chores = await this.grocy.chores();
|
|
const users: Record<string, GrocyUser> = {};
|
|
const missing: Set<number> = new Set();
|
|
chores.forEach(chore => {
|
|
if ('next_execution_assigned_user' in chore) {
|
|
users[chore.next_execution_assigned_to_user_id] = chore.next_execution_assigned_user
|
|
}
|
|
});
|
|
chores.forEach(chore => {
|
|
if (!(chore.next_execution_assigned_to_user_id in users) && typeof chore.next_execution_assigned_to_user_id === 'number') {
|
|
missing.add(chore.next_execution_assigned_to_user_id);
|
|
}
|
|
});
|
|
(await Promise.all([...missing].map(id => this.grocy.user(id)))).forEach(user => {
|
|
if (!user) {
|
|
return;
|
|
}
|
|
users[user.id] = user;
|
|
});
|
|
chores.forEach(chore => {
|
|
if (!('next_execution_assigned_user' in chore) && chore.next_execution_assigned_to_user_id in users) {
|
|
chore.next_execution_assigned_user = users[chore.next_execution_assigned_to_user_id] as GrocyUser;
|
|
}
|
|
});
|
|
|
|
res.send({
|
|
chores
|
|
});
|
|
});
|
|
|
|
app.post('/api/chores/:id', async (req, res) => {
|
|
const doer = req.body?.done_by;
|
|
const chore = req.params?.id;
|
|
if (typeof doer != 'number' || doer < 0) {
|
|
res.status(400).send({
|
|
error: 'done_by value is invalid.'
|
|
});
|
|
return;
|
|
}
|
|
if (typeof chore != 'number' || chore < 0) {
|
|
res.status(400).send({
|
|
error: 'chore id value is invalid.'
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
const resp = await this.grocy.doChore(chore, doer);
|
|
res.send(resp);
|
|
}
|
|
catch (err) {
|
|
console.error(err);
|
|
res.status(400).send({
|
|
error: 'grocy didn\'t like that.'
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/api/users/:id', async (req, res) => {
|
|
const user = await this.grocy.user(parseInt(req.params.id))
|
|
res.send({
|
|
user
|
|
});
|
|
});
|
|
|
|
this._webserver = this.app.listen(this.port, () => console.log(`chore-chart is running on port ${this.port}`));
|
|
}
|
|
|
|
close = () => {
|
|
if (this._webserver) {
|
|
this._webserver.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Web;
|
|
export { Web, notStupidParseInt };
|
|
export type { WebConfig };
|