11 Commits

20 changed files with 235 additions and 115 deletions

5
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "backend2", "name": "backend2",
"version": "1.1.0", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "backend2", "name": "backend2",
"version": "1.0.0", "version": "1.2.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
@@ -2499,6 +2499,7 @@
"version": "3.6.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "backend2", "name": "backend2",
"version": "1.1.0", "version": "1.2.0",
"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,46 @@
import { PathOrFileDescriptor, readFileSync, writeFileSync } from "node:fs";
export abstract 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
}) {
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(this.construct(this._value), undefined, 2))
}
public reload() {
this._value = JSON.parse(readFileSync(this.path, { encoding: "utf-8" }))
console.log(`Reloaded ${this.settings.name}`);
}
/**
* Method that makes sure that object is the interface.
* @param value Input object
*/
abstract construct(value: T | any): T
}

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,4 @@
import { project } from "@/utility"; import { FileHandler } from "./filehandler";
import { readFileSync, writeFileSync } from "node:fs";
export interface IUSettings { export interface IUSettings {
keyrooms: string[]; keyrooms: string[];
@@ -20,27 +19,47 @@ export interface IUSettings {
} }
} }
class UOptions { class UOptions extends FileHandler<IUSettings> {
private _settings: IUSettings; construct(value: IUSettings | any): IUSettings {
public get settings(): IUSettings { return {
return this._settings; keyrooms: value.keyrooms ?? [],
rooms: value.rooms ?? [],
cleanThings: value.cleanThings ?? [],
menu: {
defaultItems: {
sn: value.menu.defaultItems.sn ?? [],
kol: value.menu.defaultItems.kol ?? []
}
},
security: {
loginTimeout: {
attempts: value.security.loginTimeout.attempts ?? 0,
time: value.security.loginTimeout.time ?? 0,
lockout: value.security.loginTimeout.lockout ?? 0
}
}
} }
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"})
console.log("Reloaded user settings");
} }
} }

View File

@@ -22,11 +22,6 @@ declare global {
namespace Express { namespace Express {
export interface User extends IUser { export interface User extends IUser {
_id: mongoose.Types.ObjectId; _id: mongoose.Types.ObjectId;
// pass: string;
// uname: string;
// admin?: number;
// locked?: boolean;
// room?: string
} }
} }
} }
@@ -34,7 +29,7 @@ declare global {
//#region express initialization //#region express initialization
var app = express(); var app = express();
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true})) app.use(bodyParser.urlencoded({ extended: true }))
app.use(cors({ app.use(cors({
origin: ["http://localhost:4200", `https://${process.env.DOMAIN}`,], origin: ["http://localhost:4200", `https://${process.env.DOMAIN}`,],
credentials: true credentials: true
@@ -44,7 +39,7 @@ app.use(session({
rolling: true, rolling: true,
secret: process.env.SECRET, secret: process.env.SECRET,
saveUninitialized: false, saveUninitialized: false,
store: MongoStore.create({mongoUrl: connectionString, dbName: "ipwa", collectionName: "sessions", touchAfter: 60, autoRemove: 'disabled'}), store: MongoStore.create({ mongoUrl: connectionString, dbName: "ipwa", collectionName: "sessions", touchAfter: 60, autoRemove: 'disabled' }),
cookie: { cookie: {
maxAge: 1209600000, maxAge: 1209600000,
} }
@@ -53,32 +48,32 @@ app.use(passport.session())
//#endregion //#endregion
//#region Passport strategies initialization //#region Passport strategies initialization
passport.use("normal",new LocalStrategy(async function verify(uname,pass,done) { passport.use("normal", new LocalStrategy(async function verify(uname, pass, done) {
let query = await User.findOne({uname: uname.toLowerCase()}) let query = await User.findOne({ uname: uname.toLowerCase() })
if (query) { if (query) {
if (query.locked == true) return done({type: "locked", message: "Twoje konto jest zablokowane. Skontaktuj się z administratorem."}, false) if (query.locked == true) return done({ type: "locked", message: "Twoje konto jest zablokowane. Skontaktuj się z administratorem." }, false)
var timeout = security.check(query._id) var timeout = security.check(query._id)
if (timeout) { if (timeout) {
timeout = Math.ceil(timeout / 1000 / 60) timeout = Math.ceil(timeout / 1000 / 60)
return done({type: "timeout", message: `Zbyt wiele nieudanych prób logowania. Odczekaj ${timeout} minut lub skontaktuj się z administratorem.`}, false) return done({ type: "timeout", message: `Zbyt wiele nieudanych prób logowania. Odczekaj ${timeout} minut lub skontaktuj się z administratorem.` }, false)
} }
if (await bcrypt.compare(pass, query.pass)) { if (await bcrypt.compare(pass, query.pass)) {
return done(null, query) return done(null, query)
} else { } else {
security.addAttempt(query._id) security.addAttempt(query._id)
done({type: "unf"}, false) done({ type: "unf" }, false)
} }
} else { } else {
done({type: "unf"}, false) done({ type: "unf" }, false)
} }
})) }))
//#endregion //#endregion
passport.serializeUser(function(user, done) { passport.serializeUser(function (user, done) {
done(null, user._id); done(null, user._id);
}); });
passport.deserializeUser(async function(id, done) { passport.deserializeUser(async function (id, done) {
let query = await User.findById(id) let query = await User.findById(id)
if (query) { if (query) {
done(null, query) done(null, query)
@@ -89,6 +84,7 @@ passport.deserializeUser(async function(id, done) {
var server = app.listen(8080, async () => { var server = app.listen(8080, async () => {
await mongoose.connect(connectionString); await mongoose.connect(connectionString);
await dataMigration()
if (process.send) process.send("ready") if (process.send) process.send("ready")
}) })
@@ -98,3 +94,23 @@ process.on('SIGINT', () => {
server.close() server.close()
mongoose.disconnect().then(() => process.exit(0), () => process.exit(1)) mongoose.disconnect().then(() => process.exit(0), () => process.exit(1))
}) })
async function dataMigration() {
//#region User
var users = await User.find({ admin: { $type: "int" } }).lean()
users.forEach(async v => {
var oldFlags = v.admin as unknown as number
var newFlags: string[] | undefined = []
if ((oldFlags & 1) == 1) newFlags.push("news")
if ((oldFlags & 2) == 2) newFlags.push("menu")
if ((oldFlags & 4) == 4) newFlags.push("notif")
if ((oldFlags & 8) == 8) newFlags.push("groups")
if ((oldFlags & 16) == 16) newFlags.push("accs")
if ((oldFlags & 32) == 32) newFlags.push("super")
if ((oldFlags & 64) == 64) newFlags.push("keys")
if ((oldFlags & 128) == 128) newFlags.push("grades")
if (newFlags.length == 0) newFlags = undefined
await User.findByIdAndUpdate(v._id, { $set: { admin: newFlags } })
})
//#endregion
}

View File

@@ -1,8 +1,6 @@
import User from "@schemas/User"; import User from "@schemas/User";
import { Router } from "express" import { Router } from "express"
import { Perms, adminCond, adminPerm } from "@/utility"; import { Perms, adminCond, adminPerm } from "@/utility";
import capability from "@/helpers/capability";
import Group from "@/schemas/Group";
import security from "@/helpers/security"; import security from "@/helpers/security";
import { Types } from "mongoose"; import { Types } from "mongoose";
@@ -11,11 +9,7 @@ const accsRouter = Router()
accsRouter.use(adminPerm(Perms.Accs)) accsRouter.use(adminPerm(Perms.Accs))
accsRouter.get('/', async (req, res)=> { accsRouter.get('/', async (req, res)=> {
var data = { res.send(await User.find(undefined, {pass: 0}))
users: await User.find({"uname": {"$ne": req.user.uname}}, {pass: 0}),
groups: capability.settings.groups ? await Group.find() : undefined
}
res.send(data)
}) })
accsRouter.get('/:id', async (req, res) => { accsRouter.get('/:id', async (req, res) => {
@@ -27,19 +21,21 @@ accsRouter.get('/:id', async (req, res) => {
accsRouter.post('/', async (req, res)=> { accsRouter.post('/', async (req, res)=> {
if (req.body.uname == "admin") return res.status(400).send("This name is reserved").end() if (req.body.uname == "admin") return res.status(400).send("This name is reserved").end()
if (req.body.flags) { var createdUser
if (req.body.admin) {
if (adminCond(req.user.admin, Perms.Superadmin)) { if (adminCond(req.user.admin, Perms.Superadmin)) {
if (adminCond(req.body.flags, Perms.Superadmin)) { if (adminCond(req.body.admin, Perms.Superadmin)) {
res.status(400).send("Cannot set superadmin") res.status(400).send("Cannot set superadmin")
} else { } else {
await User.create({uname: req.body.uname, room: req.body.room, admin: req.body.flags, fname: req.body.fname, surname: req.body.surname}) createdUser = await User.create({uname: req.body.uname, room: req.body.room, admin: req.body.admin, fname: req.body.fname, surname: req.body.surname})
res.status(201).send({status: 201})
} }
} }
} else { } else {
await User.create({uname: req.body.uname, room: req.body.room, fname: req.body.fname, surname: req.body.surname}) createdUser = await User.create({uname: req.body.uname, room: req.body.room, fname: req.body.fname, surname: req.body.surname})
res.status(201).send({status: 201})
} }
var responseCandidate = createdUser.toJSON()
delete responseCandidate.pass
res.status(201).send(responseCandidate)
}) })
accsRouter.put('/:id', async (req, res)=> { accsRouter.put('/:id', async (req, res)=> {
@@ -48,15 +44,15 @@ accsRouter.put('/:id', async (req, res)=> {
res.status(404).send("User not found") res.status(404).send("User not found")
return return
} }
if (req.body.flags) { if (req.body.admin) {
if (adminCond(req.user.admin, Perms.Superadmin)) { if (adminCond(req.user.admin, Perms.Superadmin)) {
if (adminCond(user.admin, Perms.Superadmin)) { if (adminCond(user.admin, Perms.Superadmin)) {
res.status(400).send("Cannot edit other superadmins") res.status(400).send("Cannot edit other superadmins")
} else { } else {
if (adminCond(req.body.flags, Perms.Superadmin)) { if (adminCond(req.body.admin, Perms.Superadmin)) {
res.status(400).send("Cannot set superadmin") res.status(400).send("Cannot set superadmin")
} else { } else {
await user.set({uname: req.body.uname, room: req.body.room, admin: req.body.flags, fname: req.body.fname, surname: req.body.surname, groups: req.body.groups}).save() await user.set({uname: req.body.uname, room: req.body.room, admin: req.body.admin, fname: req.body.fname, surname: req.body.surname, groups: req.body.groups}).save()
res.send({status: 200}) res.send({status: 200})
} }
} }

View File

@@ -10,7 +10,7 @@ const cleanRouter = Router()
cleanRouter.use(adminPerm(Perms.Clean)) cleanRouter.use(adminPerm(Perms.Clean))
cleanRouter.use(capability.mw(Features.Clean)) cleanRouter.use(capability.mw(Features.Clean))
cleanRouter.get("/:date([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?([Zz]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)/:room", async (req, res) => { cleanRouter.get("/:date([0-9]{4}-[0-9]{2}-[0-9]{2})/:room", async (req, res) => {
res.send(await Grade.findOne({ res.send(await Grade.findOne({
date: new Date(req.params.date), date: new Date(req.params.date),
room: req.params.room room: req.params.room
@@ -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

@@ -9,6 +9,8 @@ import { keysRouter } from "./keys";
import { cleanRouter } from "./clean"; import { cleanRouter } from "./clean";
import { settingsRouter } from "./settings"; import { settingsRouter } from "./settings";
import User from "@/schemas/User"; import User from "@/schemas/User";
import capability from "@/helpers/capability";
import Group from "@/schemas/Group";
export const adminRouter = Router() export const adminRouter = Router()
@@ -26,3 +28,9 @@ adminRouter.get('/usearch', async (req, res) => {
var results = await User.find({$text: {$search: req.query['q'].toString()}}, {uname: 1, surname: 1, fname: 1, room: 1}) var results = await User.find({$text: {$search: req.query['q'].toString()}}, {uname: 1, surname: 1, fname: 1, room: 1})
res.send(results) res.send(results)
}) })
adminRouter.get('/sync', async (req, res) => {
res.send({
groups: capability.settings.groups ? await Group.find() : undefined
})
})

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

@@ -2,26 +2,28 @@ import { Router } from "express";
import News from "@schemas/News" import News from "@schemas/News"
import { Perms, adminPerm } from "@/utility"; import { Perms, adminPerm } from "@/utility";
import capability, { Features } from "@/helpers/capability"; import capability, { Features } from "@/helpers/capability";
import { IUser } from "@/schemas/User";
const newsRouter = Router() const newsRouter = Router()
newsRouter.use(adminPerm(Perms.News)) newsRouter.use(adminPerm(Perms.News))
newsRouter.use(capability.mw(Features.News)) newsRouter.use(capability.mw(Features.News))
newsRouter.get('/', async (req,res)=>{ newsRouter.get('/', async (req, res) => {
res.send(await News.find({},null,{sort: {pinned: -1 ,date: -1}})) var news = await News.find(undefined, undefined, { sort: { pinned: -1, date: -1 } }).populate<{ author: Pick<IUser, "fname" | "surname" | "uname"> }>("author", ["fname", "surname", "uname"])
res.send(news)
}) })
newsRouter.post('/', async (req,res)=>{ newsRouter.post('/', async (req, res) => {
await News.create({title: req.body.title, content: req.body.content}) await News.create({ title: req.body.title, content: req.body.content, author: req.user._id })
res.status(201).send({status: 201}) res.status(201).send({ status: 201 })
}) })
newsRouter.delete('/:id', async (req,res)=>{ newsRouter.delete('/:id', async (req, res) => {
await News.findByIdAndDelete(req.params.id) await News.findByIdAndDelete(req.params.id)
res.send({status: 200}) res.send({ status: 200 })
}) })
newsRouter.put('/:id', async (req,res)=>{ newsRouter.put('/:id', async (req, res) => {
await News.findByIdAndUpdate(req.params.id, req.body) await News.findByIdAndUpdate(req.params.id, { ...req.body, author: req.user._id })
res.send({status: 200}) res.send({ status: 200 })
}) })
export {newsRouter}; export { newsRouter };

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

@@ -10,13 +10,14 @@ import usettings from "@/helpers/usettings";
import Grade from "@schemas/Grade"; import Grade from "@schemas/Grade";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import Inbox from "@/schemas/Inbox"; import Inbox from "@/schemas/Inbox";
import { IUser } from "@/schemas/User";
export const appRouter = Router(); export const appRouter = Router();
appRouter.use(islogged) appRouter.use(islogged)
appRouter.get("/news", capability.mw(Features.News), async (req, res) => { appRouter.get("/news", capability.mw(Features.News), async (req, res) => {
var news = await News.find({"visible": {"$ne": false}}, {_id: 0, visible: 0}, {sort: {pinned: -1 ,date: -1}}) var news = await News.find({"visible": {"$ne": false}}, {_id: 0, visible: 0}, {sort: {pinned: -1 ,date: -1}}).populate<{author: Pick<IUser, "fname" | "surname" | "uname">}>("author", ["fname", "surname", "uname"])
res.send(news) res.send(news)
}) })
@@ -28,9 +29,9 @@ appRouter.get("/news/check", capability.mw(Features.News), async (req, res) => {
res.send(check) res.send(check)
}) })
appRouter.get("/menu/:timestamp", capability.mw(Features.Menu), async (req, res) => { appRouter.get("/menu/:date", capability.mw(Features.Menu), async (req, res) => {
var item = await Menu.aggregate(vote(new Date(Number.parseInt(req.params.timestamp)),req.user!._id)) var item = await Menu.aggregate(vote(new Date(req.params.date),req.user!._id))
var votes = await Vote.find({dom: new Date(Number.parseInt(req.params.timestamp))}) var votes = await Vote.find({dom: new Date(req.params.date)})
var grouped = votes.reduce((x, y) => { var grouped = votes.reduce((x, y) => {
x[y.tom].push(y) x[y.tom].push(y)
return x return x
@@ -62,7 +63,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

@@ -1,11 +1,12 @@
import mongoose, { Schema } from "mongoose" import mongoose, { Schema, Types } from "mongoose"
interface INews { interface INews {
content: string; content: string;
title: string; title: string;
date: Date; date: Date;
visible?: boolean; visible?: boolean;
pinned?: boolean pinned?: boolean;
author: Types.ObjectId
} }
const newsSchema = new Schema<INews>({ const newsSchema = new Schema<INews>({
@@ -13,7 +14,8 @@ const newsSchema = new Schema<INews>({
title: {type: String, required: true}, title: {type: String, required: true},
date: {type: Date, requred: true, default: Date.now}, date: {type: Date, requred: true, default: Date.now},
visible: {type: Boolean, default: false}, visible: {type: Boolean, default: false},
pinned: {type: Boolean, default: false} pinned: {type: Boolean, default: false},
author: {type: "ObjectId", ref: "logins", required: true}
}) })
export default mongoose.model("news", newsSchema) export default mongoose.model("news", newsSchema)

View File

@@ -1,10 +1,11 @@
import { Perms } from "@/utility";
import mongoose, { Types, Schema } from "mongoose" import mongoose, { Types, Schema } from "mongoose"
export interface IUser { export interface IUser {
uname: string; uname: string;
pass: string; pass: string;
room?: string; room?: string;
admin?: number; admin?: Perms[];
locked?: boolean; locked?: boolean;
fname?: string; fname?: string;
surname?: string; surname?: string;
@@ -17,7 +18,7 @@ const userSchema = new Schema<IUser>({
uname: {type: String, required: true}, uname: {type: String, required: true},
pass: {type: String, required: true, default: "$2y$10$wxDhf.XiXkmdKrFqYUEa0.F4Bf.pDykZaMmgjvyLyeRP3E/Xy0hbC"}, pass: {type: String, required: true, default: "$2y$10$wxDhf.XiXkmdKrFqYUEa0.F4Bf.pDykZaMmgjvyLyeRP3E/Xy0hbC"},
room: {type: String, default: ""}, room: {type: String, default: ""},
admin: Number, admin: [{type: String}],
locked: {type: Boolean, default: false}, locked: {type: Boolean, default: false},
fname: {type: String, default: ""}, fname: {type: String, default: ""},
surname: {type: String, default: ""}, surname: {type: String, default: ""},

View File

@@ -15,14 +15,14 @@ var isadmin = (req: Request, res: Response, next: NextFunction) => {
} }
enum Perms { enum Perms {
News = 1, News = "news",
Menu = 2, Menu = "menu",
Notif = 4, Notif = "notif",
Groups = 8, Groups = "groups",
Accs = 16, Accs = "accs",
Superadmin = 32, Superadmin = "super",
Key = 64, Key = "keys",
Clean = 128, Clean = "grades",
} }
var adminPerm = (perm: Perms) => { var adminPerm = (perm: Perms) => {
@@ -34,12 +34,13 @@ var adminPerm = (perm: Perms) => {
} }
} }
var adminCond = (adminInt = 0, perm: Perms) => { var adminCond = (perms: Perms[], perm: Perms) => {
return (adminInt & perm) == perm return perms.includes(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();