2 Commits

14 changed files with 121 additions and 50 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "backend2", "name": "backend2",
"version": "1.1.0", "version": "1.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "backend2", "name": "backend2",
"version": "1.1.0", "version": "1.1.1",
"description": "", "description": "",
"main": "src/index.js", "main": "src/index.js",
"type": "module", "type": "module",

View File

@@ -37,8 +37,21 @@ class Settings {
} }
public reloadSettings() { public reloadSettings() {
try {
this.settings = JSON.parse(readFileSync('./config/options.json', 'utf-8')) this.settings = JSON.parse(readFileSync('./config/options.json', 'utf-8'))
this.optionsToFlags() this.optionsToFlags()
} catch (error) {
if (error instanceof Error) {
if ('code' in error) {
if (error.code == "ENOENT") {
console.warn("WARNING: Capability file 'options.json' not found, enabling all modules.")
console.info("INFO: This warning will dissapear in future release of this program.")
this.settings = { clean: true, groups: true, key: true, menu: true, news: true, notif: true }
this.optionsToFlags()
}
}
}
}
} }
public get flags() : number { public get flags() : number {

View File

@@ -0,0 +1,42 @@
import { project } from "@/utility";
import { PathOrFileDescriptor, readFileSync, writeFileSync } from "node:fs";
export class FileHandler<T> {
protected _value: T
public get value(): T {
return this._value;
}
public set value(value: T) {
this._value = value
this.save()
}
constructor(public path: PathOrFileDescriptor, public settings?: {
defaultContent?: T,
name?: string,
project?: (keyof T)[] | { [key in keyof T]: any}
}) {
try {
this._value = JSON.parse(readFileSync(path, 'utf-8'))
console.log("Loaded user settings");
} catch (error) {
if (error instanceof Error) {
if ('code' in error) {
if (error.code === "ENOENT") {
writeFileSync(path, JSON.stringify(settings.defaultContent, undefined, 2))
console.log(`Created ${settings.name}`);
}
}
}
}
}
private save() {
writeFileSync(this.path, JSON.stringify(project(this._value, this.settings.project), undefined, 2))
}
public reload() {
this._value = JSON.parse(readFileSync(this.path, { encoding: "utf-8" }))
console.log(`Reloaded ${this.settings.name}`);
}
}

View File

@@ -18,8 +18,8 @@ class SecurityHelper {
if (this.timeouts.has(uid)) { if (this.timeouts.has(uid)) {
var t = this.timeouts.get(uid) var t = this.timeouts.get(uid)
t.attempts += 1 t.attempts += 1
if (t.attempts > usettings.settings.security.loginTimeout.attempts) { if (t.attempts > usettings.value.security.loginTimeout.attempts) {
this.onTimeout.set(uid, scheduleJob(new Date(Date.now() + usettings.settings.security.loginTimeout.lockout * 1000), () => { this.onTimeout.set(uid, scheduleJob(new Date(Date.now() + usettings.value.security.loginTimeout.lockout * 1000), () => {
this.onTimeout.get(uid).cancel() this.onTimeout.get(uid).cancel()
this.onTimeout.delete(uid) this.onTimeout.delete(uid)
})) }))
@@ -30,7 +30,7 @@ class SecurityHelper {
this.timeouts.set(uid, { this.timeouts.set(uid, {
attempts: 1, attempts: 1,
firstAttempt: new Date(), firstAttempt: new Date(),
expire: scheduleJob(new Date(Date.now() + usettings.settings.security.loginTimeout.time * 1000), () => { expire: scheduleJob(new Date(Date.now() + usettings.value.security.loginTimeout.time * 1000), () => {
this.timeouts.get(uid).expire.cancel() this.timeouts.get(uid).expire.cancel()
this.timeouts.delete(uid) this.timeouts.delete(uid)
}) })

View File

@@ -1,5 +1,6 @@
import { project } from "@/utility"; import { project } from "@/utility";
import { readFileSync, writeFileSync } from "node:fs"; import { readFileSync, writeFileSync } from "node:fs";
import { FileHandler } from "./filehandler";
export interface IUSettings { export interface IUSettings {
keyrooms: string[]; keyrooms: string[];
@@ -20,27 +21,27 @@ export interface IUSettings {
} }
} }
class UOptions { class UOptions extends FileHandler<IUSettings> {
private _settings: IUSettings;
public get settings(): IUSettings {
return this._settings;
}
public set settings(value: IUSettings) {
this._settings = project<typeof value>(value, ['cleanThings', 'keyrooms', 'menu', 'rooms', 'security']) as typeof value
this.save()
}
constructor() { constructor() {
this.reload() const defaultSettings: IUSettings = {
keyrooms: [],
rooms: [],
cleanThings: [],
menu: {
defaultItems: {
sn: [],
kol: [],
} }
},
private save() { security: {
writeFileSync("./config/usettings.json", JSON.stringify(this._settings, undefined, 2)) loginTimeout: {
attempts: 0,
time: 0,
lockout: 0
} }
}
reload() { }
this.settings = JSON.parse(readFileSync("./config/usettings.json", {encoding: "utf-8"})) super("./config/usettings.json", {defaultContent: defaultSettings, name: "user settings", project: ['cleanThings', 'keyrooms', 'menu', 'rooms', 'security']})
console.log("Reloaded user settings");
} }
} }

View File

@@ -65,8 +65,8 @@ cleanRouter.delete("/:id", async (req, res) => {
cleanRouter.get('/config', (req, res) => { cleanRouter.get('/config', (req, res) => {
res.send({ res.send({
rooms: usettings.settings.rooms, rooms: usettings.value.rooms,
things: usettings.settings.cleanThings things: usettings.value.cleanThings
}) })
}) })
@@ -88,7 +88,7 @@ cleanRouter.delete('/attendence/:room', async (req, res) => {
}) })
cleanRouter.get('/attendenceSummary', async (req, res) => { cleanRouter.get('/attendenceSummary', async (req, res) => {
var allRooms = usettings.settings.rooms var allRooms = usettings.value.rooms
var graded = (await Grade.find({date: new Date().setUTCHours(24,0,0,0)})).map(v => v.room) var graded = (await Grade.find({date: new Date().setUTCHours(24,0,0,0)})).map(v => v.room)
var ungraded = allRooms.filter(x => !graded.includes(x)) var ungraded = allRooms.filter(x => !graded.includes(x))
var summary = attendence.summary() var summary = attendence.summary()

View File

@@ -34,7 +34,7 @@ keysRouter.post("/", async (req, res) => {
keysRouter.get("/available", async (req, res) => { keysRouter.get("/available", async (req, res) => {
var taken = await Key.find({tb: {$exists: false}}, {}, {sort: {borrow: -1}}) var taken = await Key.find({tb: {$exists: false}}, {}, {sort: {borrow: -1}})
var occ = Array.from(new Set(taken.map((v) => v.room))) var occ = Array.from(new Set(taken.map((v) => v.room)))
var all = Array.from(new Set(usettings.settings.keyrooms)) var all = Array.from(new Set(usettings.value.keyrooms))
var free = all.filter(x => !occ.includes(x)) var free = all.filter(x => !occ.includes(x))
res.send(free) res.send(free)
}) })

View File

@@ -62,7 +62,7 @@ menuRouter.get('/print', async (req, res) => {
var meals = await Menu.find({day: {$gte: start, $lte: end}}, undefined, {sort: {day: 1}}) var meals = await Menu.find({day: {$gte: start, $lte: end}}, undefined, {sort: {day: 1}})
var doc = meals.map(s => `<tr> var doc = meals.map(s => `<tr>
<th>${dayName(s.day)}<br>${s.day.getDate()}.${s.day.getMonth()}.${s.day.getFullYear()}r.<br>${s.dayTitle}</th> <th>${dayName(s.day)}<br>${s.day.getDate()}.${s.day.getMonth()}.${s.day.getFullYear()}r.<br>${s.dayTitle}</th>
<td>${usettings.settings.menu.defaultItems.sn.join('<br>')}<br>${s.sn.fancy.join('<br>')}<br>${s.sn.second}</td> <td>${usettings.value.menu.defaultItems.sn.join('<br>')}<br>${s.sn.fancy.join('<br>')}<br>${s.sn.second}</td>
<td> <td>
<b>Z:</b> ${s.ob.soup}<br> <b>Z:</b> ${s.ob.soup}<br>
<b>V:</b> ${s.ob.vege}<br> <b>V:</b> ${s.ob.vege}<br>
@@ -71,7 +71,7 @@ menuRouter.get('/print', async (req, res) => {
${s.ob.drink}<br> ${s.ob.drink}<br>
${s.ob.other.join('<br>')} ${s.ob.other.join('<br>')}
</td> </td>
<td>${s.day.getUTCDay() == 5 ? "<b>Kolacja w domu!</b>" : `${usettings.settings.menu.defaultItems.kol.join('<br>')}<br>${s.kol}`}</td> <td>${s.day.getUTCDay() == 5 ? "<b>Kolacja w domu!</b>" : `${usettings.value.menu.defaultItems.kol.join('<br>')}<br>${s.kol}`}</td>
</tr>`) </tr>`)
var html = `<html><head><meta charset="UTF-8"><style>table,th,td{border: 0.4ch solid;}td{line-height: 1;}</style></head><body><table><caption>Jadłospis dekadowy</caption><thead><tr><th>Dzień</th><th>Śniadanie</th><th>Obiad</th><th>Kolacja</th></tr></thead><tbody>${doc.join('\n')}</tbody></table></body></html>` var html = `<html><head><meta charset="UTF-8"><style>table,th,td{border: 0.4ch solid;}td{line-height: 1;}</style></head><body><table><caption>Jadłospis dekadowy</caption><thead><tr><th>Dzień</th><th>Śniadanie</th><th>Obiad</th><th>Kolacja</th></tr></thead><tbody>${doc.join('\n')}</tbody></table></body></html>`
res.type('html').send(html) res.type('html').send(html)

View File

@@ -7,11 +7,11 @@ export const settingsRouter = Router()
settingsRouter.use(adminPerm(Perms.Superadmin)) settingsRouter.use(adminPerm(Perms.Superadmin))
settingsRouter.get('/', (req, res) => { settingsRouter.get('/', (req, res) => {
res.send(usettings.settings) res.send(usettings.value)
}) })
settingsRouter.post('/', (req, res) => { settingsRouter.post('/', (req, res) => {
usettings.settings = req.body usettings.value = req.body
res.send({status: 200}) res.send({status: 200})
}) })

View File

@@ -62,7 +62,7 @@ appRouter.post("/menu/:timestamp", capability.mw(Features.Menu), async (req, res
appRouter.get("/keys", capability.mw(Features.Key), async (req, res) => { appRouter.get("/keys", capability.mw(Features.Key), async (req, res) => {
var keys = await Key.find<Pick<IKey, "room">>({tb: {$exists: false}}, {room: 1}, {sort: {room: 1}}) var keys = await Key.find<Pick<IKey, "room">>({tb: {$exists: false}}, {room: 1}, {sort: {room: 1}})
var occ = keys.map(x=>x.room) var occ = keys.map(x=>x.room)
var all = usettings.settings.keyrooms var all = usettings.value.keyrooms
var free = all.filter(x=>!occ.includes(x)).sort().map(x => { var free = all.filter(x=>!occ.includes(x)).sort().map(x => {
return { room: x } return { room: x }
}) })

View File

@@ -82,7 +82,7 @@ authRouter.get("/check", islogged, (req, res, next) => {
res.status(401).send({status: 401, message: "Your account has been locked."}) res.status(401).send({status: 401, message: "Your account has been locked."})
}) })
} }
res.send({"admin": req.user.admin, "features": cap.flags, "room": req.user.room, "menu": {"defaultItems": usettings.settings.menu.defaultItems}, "vapid": vapidKeys.keys.publicKey}) res.send({"admin": req.user.admin, "features": cap.flags, "room": req.user.room, "menu": {"defaultItems": usettings.value.menu.defaultItems}, "vapid": vapidKeys.keys.publicKey})
}) })
authRouter.put("/redirect", islogged, async (req, res) => { authRouter.put("/redirect", islogged, async (req, res) => {

View File

@@ -38,8 +38,9 @@ var adminCond = (adminInt = 0, perm: Perms) => {
return (adminInt & perm) == perm return (adminInt & perm) == perm
} }
export function project<T extends object>(obj: T | any, projection: (keyof T)[] | { [key in keyof T]: any}): Partial<T> { export function project<T extends object>(obj: T | any, projection?: (keyof T)[] | { [key in keyof T]: any}): Partial<T> {
let obj2: Partial<T> = {} let obj2: Partial<T> = {}
if (projection) {
if (projection instanceof Array) { if (projection instanceof Array) {
for (let key of projection) { for (let key of projection) {
if (key in obj) obj2[key] = obj[key] if (key in obj) obj2[key] = obj[key]
@@ -50,6 +51,9 @@ export function project<T extends object>(obj: T | any, projection: (keyof T)[]
} }
} }
return obj2 return obj2
} else {
return obj
}
} }
export {islogged, isadmin, adminPerm, Perms, adminCond}; export {islogged, isadmin, adminPerm, Perms, adminCond};

View File

@@ -20,12 +20,23 @@ class VapidKeysSettings {
} }
reload() { reload() {
try {
this._keys = JSON.parse(readFileSync("./config/keys.json", {encoding: "utf-8"})) this._keys = JSON.parse(readFileSync("./config/keys.json", {encoding: "utf-8"}))
if (!(this._keys.privateKey && this._keys.publicKey)) { } catch (error) {
if (error instanceof Error) {
if ('code' in error) {
if (error.code === "ENOENT") {
this.keys = generateVAPIDKeys();
}
}
}
} finally {
if (!(this.keys.privateKey && this.keys.publicKey)) {
this.keys = generateVAPIDKeys() this.keys = generateVAPIDKeys()
} }
console.log("Reloaded VAPID keys"); console.log("Reloaded VAPID keys");
} }
}
} }
export default new VapidKeysSettings(); export default new VapidKeysSettings();