Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
453ff9094f
|
|||
|
8fc171b874
|
|||
|
9e420e548e
|
|||
|
2db19ee1e7
|
|||
|
3ba7460eee
|
|||
|
681881097b
|
|||
|
4d4c3993d9
|
|||
|
e8b4e4634f
|
|||
|
647bc1c2ba
|
|||
|
aac85e3679
|
|||
|
93183799af
|
|||
| b299963570 | |||
|
5b87634813
|
|||
|
b7c84fbe14
|
|||
|
d50aa79c5d
|
|||
|
df745c78e2
|
|||
|
ec24700b8a
|
|||
|
334402d8c5
|
|||
|
dd17fd5294
|
|||
|
ec787e2321
|
|||
|
4de7af4bc0
|
|||
|
432b4dc4e5
|
|||
|
92aaee9bcc
|
|||
|
b708fe8c18
|
|||
|
9efeba0010
|
|||
| 8f0f1efb88 |
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "backend2",
|
"name": "backend2",
|
||||||
"version": "1.0.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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "backend2",
|
"name": "backend2",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Job, scheduleJob } from "node-schedule";
|
|
||||||
|
|
||||||
class Attendence {
|
|
||||||
private attendence = new Map<string, {_id: string, hour?: string}[]>();
|
|
||||||
private job: Job
|
|
||||||
constructor () {
|
|
||||||
this.job = scheduleJob("0 0 * * *", () => {
|
|
||||||
this.attendence.clear()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setRoom (room: string, att: {_id: string, hour?: string}[]) {
|
|
||||||
this.attendence.set(room, att)
|
|
||||||
}
|
|
||||||
|
|
||||||
getRoom (room: string) {
|
|
||||||
return this.attendence.get(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
summary () {
|
|
||||||
var summary: {room: string, hours: string[]}[] = []
|
|
||||||
this.attendence.forEach((v, k) => {
|
|
||||||
summary.push({room: k, hours: v.map(i => i.hour)})
|
|
||||||
})
|
|
||||||
return summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Attendence()
|
|
||||||
41
src/helpers/attendence.ts
Normal file
41
src/helpers/attendence.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Job, scheduleJob } from "node-schedule";
|
||||||
|
|
||||||
|
interface IAttendence {
|
||||||
|
auto: {
|
||||||
|
_id: string;
|
||||||
|
hour?: string
|
||||||
|
}[];
|
||||||
|
notes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Attendence {
|
||||||
|
private attendence = new Map<string, IAttendence>();
|
||||||
|
private job: Job
|
||||||
|
constructor () {
|
||||||
|
this.job = scheduleJob("0 0 * * *", () => {
|
||||||
|
this.attendence.clear()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoom (room: string, att: IAttendence) {
|
||||||
|
this.attendence.set(room, att)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRoom (room: string) {
|
||||||
|
this.attendence.delete(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoom (room: string): IAttendence | undefined {
|
||||||
|
return this.attendence.get(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
summary () {
|
||||||
|
var summary: {room: string, hours: string[], notes: string, auto: boolean}[] = []
|
||||||
|
this.attendence.forEach((v, k) => {
|
||||||
|
summary.push({room: k, hours: v.auto.map(i => i.hour), notes: v.notes, auto: false})
|
||||||
|
})
|
||||||
|
return summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Attendence()
|
||||||
@@ -37,8 +37,21 @@ class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public reloadSettings() {
|
public reloadSettings() {
|
||||||
this.settings = JSON.parse(readFileSync('./config/options.json', 'utf-8'))
|
try {
|
||||||
this.optionsToFlags()
|
this.settings = JSON.parse(readFileSync('./config/options.json', 'utf-8'))
|
||||||
|
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 {
|
||||||
46
src/helpers/filehandler.ts
Normal file
46
src/helpers/filehandler.ts
Normal 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
|
||||||
|
}
|
||||||
57
src/helpers/security.ts
Normal file
57
src/helpers/security.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Job, scheduleJob } from "node-schedule";
|
||||||
|
import usettings from "./usettings";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
interface IAccTimeout {
|
||||||
|
firstAttempt: Date;
|
||||||
|
expire: Job;
|
||||||
|
attempts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecurityHelper {
|
||||||
|
private timeouts = new Map<string, IAccTimeout>();
|
||||||
|
private onTimeout = new Map<string, Job>(); // key: user id, value: unlock date
|
||||||
|
constructor () { }
|
||||||
|
|
||||||
|
addAttempt (userId: Types.ObjectId) {
|
||||||
|
var uid = userId.toString()
|
||||||
|
if (this.timeouts.has(uid)) {
|
||||||
|
var t = this.timeouts.get(uid)
|
||||||
|
t.attempts += 1
|
||||||
|
if (t.attempts > usettings.value.security.loginTimeout.attempts) {
|
||||||
|
this.onTimeout.set(uid, scheduleJob(new Date(Date.now() + usettings.value.security.loginTimeout.lockout * 1000), () => {
|
||||||
|
this.onTimeout.get(uid).cancel()
|
||||||
|
this.onTimeout.delete(uid)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
this.timeouts.set(uid, t)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.timeouts.set(uid, {
|
||||||
|
attempts: 1,
|
||||||
|
firstAttempt: new Date(),
|
||||||
|
expire: scheduleJob(new Date(Date.now() + usettings.value.security.loginTimeout.time * 1000), () => {
|
||||||
|
this.timeouts.get(uid).expire.cancel()
|
||||||
|
this.timeouts.delete(uid)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(userId: Types.ObjectId) {
|
||||||
|
var timeout = this.onTimeout.get(userId.toString())
|
||||||
|
if (timeout) {
|
||||||
|
// @ts-ignore
|
||||||
|
return timeout.nextInvocation().toDate().valueOf() - Date.now().valueOf()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAcc(userId: string) {
|
||||||
|
return this.onTimeout.delete(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SecurityHelper()
|
||||||
66
src/helpers/usettings.ts
Normal file
66
src/helpers/usettings.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { FileHandler } from "./filehandler";
|
||||||
|
|
||||||
|
export interface IUSettings {
|
||||||
|
keyrooms: string[];
|
||||||
|
rooms: string[];
|
||||||
|
cleanThings: string[];
|
||||||
|
menu: {
|
||||||
|
defaultItems: {
|
||||||
|
sn: string[];
|
||||||
|
kol: string[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
loginTimeout: {
|
||||||
|
attempts: number;
|
||||||
|
time: number;
|
||||||
|
lockout: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UOptions extends FileHandler<IUSettings> {
|
||||||
|
construct(value: IUSettings | any): IUSettings {
|
||||||
|
return {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
const defaultSettings: IUSettings = {
|
||||||
|
keyrooms: [],
|
||||||
|
rooms: [],
|
||||||
|
cleanThings: [],
|
||||||
|
menu: {
|
||||||
|
defaultItems: {
|
||||||
|
sn: [],
|
||||||
|
kol: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
loginTimeout: {
|
||||||
|
attempts: 0,
|
||||||
|
time: 0,
|
||||||
|
lockout: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super("./config/usettings.json", {defaultContent: defaultSettings, name: "user settings"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UOptions();
|
||||||
64
src/index.ts
64
src/index.ts
@@ -7,9 +7,10 @@ import session from "express-session";
|
|||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import MongoStore from "connect-mongo";
|
import MongoStore from "connect-mongo";
|
||||||
import mongoose from "mongoose"
|
import mongoose from "mongoose"
|
||||||
import User from "./schemas/User";
|
import User, { IUser } from "./schemas/User";
|
||||||
import routes from "./routes/index";
|
import routes from "./routes/index";
|
||||||
import process from "node:process"
|
import process from "node:process"
|
||||||
|
import security from "./helpers/security";
|
||||||
const connectionString = process.env.ATLAS_URI || "mongodb://mongodb:27017/ipwa";
|
const connectionString = process.env.ATLAS_URI || "mongodb://mongodb:27017/ipwa";
|
||||||
|
|
||||||
if (!process.env.DOMAIN) {
|
if (!process.env.DOMAIN) {
|
||||||
@@ -19,13 +20,8 @@ if (!process.env.DOMAIN) {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
export interface User {
|
export interface User extends IUser {
|
||||||
_id: mongoose.Types.ObjectId;
|
_id: mongoose.Types.ObjectId;
|
||||||
pass: string;
|
|
||||||
uname: string;
|
|
||||||
admin?: number;
|
|
||||||
locked?: boolean;
|
|
||||||
room?: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,9 +29,9 @@ 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", "http://localhost:3000", `https://${process.env.DOMAIN}`,],
|
origin: ["http://localhost:4200", `https://${process.env.DOMAIN}`,],
|
||||||
credentials: true
|
credentials: true
|
||||||
}))
|
}))
|
||||||
app.use(session({
|
app.use(session({
|
||||||
@@ -43,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,
|
||||||
}
|
}
|
||||||
@@ -52,24 +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.locked == true) return done(null, false)
|
|
||||||
if (query) {
|
if (query) {
|
||||||
|
if (query.locked == true) return done({ type: "locked", message: "Twoje konto jest zablokowane. Skontaktuj się z administratorem." }, false)
|
||||||
|
var timeout = security.check(query._id)
|
||||||
|
if (timeout) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
if (await bcrypt.compare(pass, query.pass)) {
|
if (await bcrypt.compare(pass, query.pass)) {
|
||||||
return done(null, query)
|
return done(null, query)
|
||||||
} else done(null, false)
|
} else {
|
||||||
|
security.addAttempt(query._id)
|
||||||
|
done({ type: "unf" }, false)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
done(null, 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)
|
||||||
@@ -78,13 +82,35 @@ passport.deserializeUser(async function(id, done) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('/', routes)
|
app.use('/', routes)
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
131
src/notif.ts
131
src/notif.ts
@@ -1,12 +1,27 @@
|
|||||||
import { PushSubscription, RequestOptions, VapidKeys, sendNotification } from "web-push";
|
import { RequestOptions, SendResult, VapidKeys, WebPushError, sendNotification } from "web-push";
|
||||||
import Notification from "./schemas/Notification";
|
import Notification from "./schemas/Notification";
|
||||||
import vapidKeys from "./vapidKeys";
|
import vapidKeys from "./vapidKeys";
|
||||||
|
import User, { IUser } from "./schemas/User";
|
||||||
|
import Inbox from "./schemas/Inbox";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IUser } from "./schemas/User";
|
|
||||||
|
|
||||||
export class NotifcationHelper {
|
export interface SimpleMessage {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PushResult {
|
||||||
|
sent: number;
|
||||||
|
possible: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Message {
|
||||||
private options: RequestOptions
|
private options: RequestOptions
|
||||||
constructor () {
|
private message: { notification: SimpleMessage }
|
||||||
|
private rcptType: "uid" | "room" | "group"
|
||||||
|
private rcpt: string
|
||||||
|
|
||||||
|
constructor (title: string, body: string, rcptType: "uid" | "room" | "group", rcpt: string) {
|
||||||
let keys: VapidKeys = vapidKeys.keys
|
let keys: VapidKeys = vapidKeys.keys
|
||||||
this.options = {
|
this.options = {
|
||||||
vapidDetails: {
|
vapidDetails: {
|
||||||
@@ -15,58 +30,76 @@ export class NotifcationHelper {
|
|||||||
publicKey: keys.publicKey
|
publicKey: keys.publicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.message = { notification: { title: title, body: body } }
|
||||||
|
this.rcptType = rcptType
|
||||||
|
this.rcpt = rcpt
|
||||||
}
|
}
|
||||||
|
|
||||||
private async send(message: string, subscriptions: PushSubscription[]) {
|
async findUserNotif(uid: string) {
|
||||||
var count = 0;
|
var notif = await Notification.find().populate<{user: Pick<IUser, 'uname'> & {_id: Types.ObjectId}}>('user', ['uname', '_id']).exec()
|
||||||
var subslen = subscriptions.length
|
return notif.filter(val => val.user._id.toString() == uid)
|
||||||
for (const v of subscriptions) {
|
|
||||||
var result
|
|
||||||
try {
|
|
||||||
result = await sendNotification(v, message, this.options)
|
|
||||||
count++
|
|
||||||
} catch (error) {
|
|
||||||
if (error.statusCode == 410) {
|
|
||||||
console.log("GONE")
|
|
||||||
await Notification.findOneAndDelete({endpoint: v.endpoint, keys: v.keys})
|
|
||||||
subslen--
|
|
||||||
}
|
|
||||||
else console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {sent: count, possible: subslen}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private rcpt(message: string) {
|
async findRoomNotif(room: string) {
|
||||||
return {
|
var notif = await Notification.find().populate<{user: Pick<IUser, 'room'> & {_id: Types.ObjectId}}>('user', ['room', '_id']).exec()
|
||||||
user: async (uname: string) => {
|
|
||||||
return await this.send(message, await this.findUserNotif(uname))
|
|
||||||
},
|
|
||||||
room: async (room: string) => {
|
|
||||||
return await this.send(message, await this.findRoomNotif(room))
|
|
||||||
},
|
|
||||||
group: async (group: string) => {
|
|
||||||
return await this.send(message, await this.findGroupNotif(group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
simpleMessage(title: string, body: string) {
|
|
||||||
return this.rcpt(JSON.stringify({notification: {title: title, body: body}}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async findUserNotif(uname: string): Promise<Array<any>> {
|
|
||||||
var notif = await Notification.find().populate<{user: Pick<IUser, 'uname'>}>('user', 'uname').exec()
|
|
||||||
return notif.filter(val => val.user.uname == uname)
|
|
||||||
}
|
|
||||||
|
|
||||||
async findRoomNotif(room: string): Promise<Array<any>> {
|
|
||||||
var notif = await Notification.find().populate<{user: Pick<IUser, 'room'>}>('user', 'room').exec()
|
|
||||||
return notif.filter(val => val.user.room == room)
|
return notif.filter(val => val.user.room == room)
|
||||||
}
|
}
|
||||||
|
|
||||||
async findGroupNotif(groupId: string): Promise<Array<any>> {
|
async findGroupNotif(groupId: string) {
|
||||||
var notif = await Notification.find().populate<{user: Pick<IUser, 'groups'>}>('user', 'groups').exec()
|
var notif = await Notification.find().populate<{user: Pick<IUser, 'groups'> & {_id: Types.ObjectId}}>('user', ['groups', '_id']).exec()
|
||||||
return notif.filter(val => val.user.groups.find(x => x.toString() == groupId))
|
return notif.filter(val => val.user.groups.find(x => x.toString() == groupId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async send(): Promise<PushResult> {
|
||||||
|
var subscriptions
|
||||||
|
var rcptIds: Types.ObjectId[]
|
||||||
|
switch (this.rcptType) {
|
||||||
|
case "uid":
|
||||||
|
subscriptions = await this.findUserNotif(this.rcpt)
|
||||||
|
rcptIds = [new Types.ObjectId(this.rcpt)]
|
||||||
|
break;
|
||||||
|
case "room":
|
||||||
|
subscriptions = await this.findRoomNotif(this.rcpt)
|
||||||
|
rcptIds = (await User.find({room: this.rcpt})).map(v => v._id)
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
subscriptions = await this.findGroupNotif(this.rcpt)
|
||||||
|
rcptIds = (await User.find({groups: this.rcpt})).map(v => v._id)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Wrong recipient type used: ${this.rcptType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Inbox.create({message: this.message.notification, rcpt: rcptIds})
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var subslen = subscriptions.length
|
||||||
|
for (const v of subscriptions) {
|
||||||
|
var result: SendResult
|
||||||
|
try {
|
||||||
|
result = await sendNotification(v, JSON.stringify(this.message), this.options)
|
||||||
|
count++
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof WebPushError) {
|
||||||
|
switch (error.statusCode) {
|
||||||
|
case 410:
|
||||||
|
console.log("GONE")
|
||||||
|
await Notification.findByIdAndRemove(v._id)
|
||||||
|
subslen--
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
console.warn("NOT FOUND", error.message)
|
||||||
|
await Notification.findByIdAndRemove(v._id)
|
||||||
|
subslen--
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(error)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {sent: count, possible: subslen}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
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 "@/capability";
|
import security from "@/helpers/security";
|
||||||
import Group from "@/schemas/Group";
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
const accsRouter = Router()
|
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
|
|
||||||
}
|
accsRouter.get('/:id', async (req, res) => {
|
||||||
res.send(data)
|
res.send({
|
||||||
|
...(await User.findById(req.params.id, {pass: 0})).toJSON(),
|
||||||
|
lockout: !!security.check(new Types.ObjectId(req.params.id))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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)=> {
|
||||||
@@ -39,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 != undefined) {
|
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})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,4 +87,12 @@ accsRouter.delete('/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
accsRouter.delete('/:id/lockout', async (req, res) => {
|
||||||
|
if (security.clearAcc(req.params.id)) {
|
||||||
|
res.send({status: 200}).end()
|
||||||
|
} else {
|
||||||
|
res.sendStatus(400)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export {accsRouter};
|
export {accsRouter};
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { Perms, adminPerm } from "@/utility";
|
import { Perms, adminPerm } from "@/utility";
|
||||||
import capability, { Features } from "@/capability";
|
import capability, { Features } from "@/helpers/capability";
|
||||||
import usettings from "@/usettings";
|
import usettings from "@/helpers/usettings";
|
||||||
import Grade from "@schemas/Grade";
|
import Grade from "@schemas/Grade";
|
||||||
import User from "@/schemas/User";
|
import User from "@/schemas/User";
|
||||||
import attendence from "@/attendence";
|
import attendence from "@/helpers/attendence";
|
||||||
|
|
||||||
const cleanRouter = Router()
|
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
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -82,8 +82,18 @@ cleanRouter.post('/attendence/:room', async (req, res) => {
|
|||||||
res.send({status: 200})
|
res.send({status: 200})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cleanRouter.delete('/attendence/:room', async (req, res) => {
|
||||||
|
attendence.clearRoom(req.params.room)
|
||||||
|
res.send({status: 200})
|
||||||
|
})
|
||||||
|
|
||||||
cleanRouter.get('/attendenceSummary', async (req, res) => {
|
cleanRouter.get('/attendenceSummary', async (req, res) => {
|
||||||
res.send(attendence.summary())
|
var allRooms = usettings.value.rooms
|
||||||
|
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 summary = attendence.summary()
|
||||||
|
var unchecked: typeof summary = ungraded.filter(x => !summary.map(v => v.room).includes(x)).map(v => ({room: v, hours: [] as string[], notes: "Nie sprawdzono", auto: true}))
|
||||||
|
res.send([...summary, ...unchecked])
|
||||||
})
|
})
|
||||||
|
|
||||||
export {cleanRouter}
|
export {cleanRouter}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Group from "@schemas/Group";
|
import Group from "@schemas/Group";
|
||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import { Perms, adminPerm } from "@/utility";
|
import { Perms, adminPerm } from "@/utility";
|
||||||
import capability, { Features } from "@/capability";
|
import capability, { Features } from "@/helpers/capability";
|
||||||
|
|
||||||
const groupsRouter = Router()
|
const groupsRouter = Router()
|
||||||
|
|
||||||
|
|||||||
36
src/routes/api/admin/index.ts
Normal file
36
src/routes/api/admin/index.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { islogged, isadmin} from "@/utility";
|
||||||
|
import { newsRouter } from "./news";
|
||||||
|
import { accsRouter } from "./accs";
|
||||||
|
import { menuRouter } from "./menu";
|
||||||
|
import { groupsRouter } from "./groups";
|
||||||
|
import { notifRouter } from "./notif";
|
||||||
|
import { keysRouter } from "./keys";
|
||||||
|
import { cleanRouter } from "./clean";
|
||||||
|
import { settingsRouter } from "./settings";
|
||||||
|
import User from "@/schemas/User";
|
||||||
|
import capability from "@/helpers/capability";
|
||||||
|
import Group from "@/schemas/Group";
|
||||||
|
|
||||||
|
export const adminRouter = Router()
|
||||||
|
|
||||||
|
adminRouter.use(islogged, isadmin)
|
||||||
|
adminRouter.use('/news', newsRouter)
|
||||||
|
adminRouter.use('/accs', accsRouter)
|
||||||
|
adminRouter.use('/menu', menuRouter)
|
||||||
|
adminRouter.use('/groups', groupsRouter)
|
||||||
|
adminRouter.use('/notif', notifRouter)
|
||||||
|
adminRouter.use('/keys', keysRouter)
|
||||||
|
adminRouter.use('/clean', cleanRouter)
|
||||||
|
adminRouter.use('/settings', settingsRouter)
|
||||||
|
|
||||||
|
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})
|
||||||
|
res.send(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
adminRouter.get('/sync', async (req, res) => {
|
||||||
|
res.send({
|
||||||
|
groups: capability.settings.groups ? await Group.find() : undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import capability, { Features } from "@/capability";
|
import capability, { Features } from "@/helpers/capability";
|
||||||
import Key from "@schemas/Key";
|
import Key from "@schemas/Key";
|
||||||
import usettings from "@/usettings";
|
import usettings from "@/helpers/usettings";
|
||||||
import User from "@schemas/User";
|
import User, { IUser } from "@schemas/User";
|
||||||
import { Perms, adminPerm } from "@/utility";
|
import { Perms, adminPerm } from "@/utility";
|
||||||
|
|
||||||
const keysRouter = Router()
|
const keysRouter = Router()
|
||||||
@@ -11,22 +11,20 @@ keysRouter.use(capability.mw(Features.Key))
|
|||||||
keysRouter.use(adminPerm(Perms.Key))
|
keysRouter.use(adminPerm(Perms.Key))
|
||||||
|
|
||||||
keysRouter.get("/", async (req, res) => {
|
keysRouter.get("/", async (req, res) => {
|
||||||
var keys = await Key.find({}, {}, {sort: {borrow: -1}}).populate("whom", {uname: 1, _id: 1, room: 1})
|
var keys = await Key.find({}, undefined, {sort: {borrow: -1}}).populate<Pick<IUser, "uname" | "room"> & {_id: string}>({path: "whom", select: { _id: 1, uname: 1, room: 1}})
|
||||||
res.send(keys)
|
res.send(keys)
|
||||||
})
|
})
|
||||||
|
|
||||||
keysRouter.post("/", async (req, res) => {
|
keysRouter.post("/", async (req, res) => {
|
||||||
var newKey: {
|
var user = await User.findById(req.body.whom._id)
|
||||||
room: string;
|
if (!user) {
|
||||||
whom: string;
|
|
||||||
} = req.body
|
|
||||||
var user = await User.findOne({uname: newKey.whom})
|
|
||||||
if (user) {
|
|
||||||
newKey.whom = user._id.toString()
|
|
||||||
} else {
|
|
||||||
return res.status(404).send("User not found").end()
|
return res.status(404).send("User not found").end()
|
||||||
}
|
}
|
||||||
if (await Key.create(newKey)) {
|
const newKey = new Key({
|
||||||
|
room: req.body.room,
|
||||||
|
whom: user._id
|
||||||
|
})
|
||||||
|
if (await newKey.save()) {
|
||||||
res.status(201).send({status: 201})
|
res.status(201).send({status: 201})
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(500)
|
res.sendStatus(500)
|
||||||
@@ -36,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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import multer from "multer"
|
|||||||
import * as XLSX from "xlsx"
|
import * as XLSX from "xlsx"
|
||||||
import Menu from "@schemas/Menu"
|
import Menu from "@schemas/Menu"
|
||||||
import Vote from "@schemas/Vote"
|
import Vote from "@schemas/Vote"
|
||||||
import capability, { Features } from "@/capability"
|
import capability, { Features } from "@/helpers/capability"
|
||||||
import { editorRouter } from "./editor"
|
import { editorRouter } from "./editor"
|
||||||
import usettings from "@/usettings"
|
import usettings from "@/helpers/usettings"
|
||||||
|
|
||||||
const menuRouter = Router()
|
const menuRouter = Router()
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
import { Router } from "express";
|
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 "@/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 };
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
import { Perms, adminPerm } from "@/utility";
|
|
||||||
import Group from "@schemas/Group";
|
|
||||||
import { NotifcationHelper } from "@/notif";
|
|
||||||
import capability, { Features } from "@/capability";
|
|
||||||
|
|
||||||
const notifRouter = Router()
|
|
||||||
|
|
||||||
const nh = new NotifcationHelper()
|
|
||||||
|
|
||||||
notifRouter.use(adminPerm(Perms.Notif))
|
|
||||||
notifRouter.use(capability.mw(Features.Notif))
|
|
||||||
|
|
||||||
notifRouter.post("/send", async (req, res) => {
|
|
||||||
const message = nh.simpleMessage(req.body.title, req.body.body)
|
|
||||||
let recp: string
|
|
||||||
let result;
|
|
||||||
switch (req.body.recp.type) {
|
|
||||||
case "uname":
|
|
||||||
recp = req.body.recp.uname
|
|
||||||
result = await message.user(recp);
|
|
||||||
break;
|
|
||||||
case "room":
|
|
||||||
recp = req.body.recp.room
|
|
||||||
result = await message.room(recp)
|
|
||||||
break;
|
|
||||||
case "group":
|
|
||||||
if (!capability.settings.groups) return res.sendStatus(406).end()
|
|
||||||
recp = req.body.recp.group
|
|
||||||
result = await message.group(recp)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.status(400).end()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(`
|
|
||||||
From: ${req.user.uname} (${req.user._id})
|
|
||||||
To: ${recp}
|
|
||||||
Subject: ${req.body.title}
|
|
||||||
|
|
||||||
${req.body.body}
|
|
||||||
`);
|
|
||||||
res.send(result)
|
|
||||||
})
|
|
||||||
|
|
||||||
notifRouter.get("/groups", async (req,res) => {
|
|
||||||
res.send(await Group.find({}, {name: 1, _id: 1}))
|
|
||||||
})
|
|
||||||
|
|
||||||
export {notifRouter}
|
|
||||||
56
src/routes/api/admin/notif/index.ts
Normal file
56
src/routes/api/admin/notif/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
import { Perms, adminPerm } from "@/utility";
|
||||||
|
import Group from "@schemas/Group";
|
||||||
|
import { PushResult, Message } from "@/notif";
|
||||||
|
import capability, { Features } from "@/helpers/capability";
|
||||||
|
import { outboxRouter } from "./outbox";
|
||||||
|
|
||||||
|
const notifRouter = Router()
|
||||||
|
|
||||||
|
notifRouter.use(adminPerm(Perms.Notif))
|
||||||
|
notifRouter.use(capability.mw(Features.Notif))
|
||||||
|
|
||||||
|
type PushSendBody = {recp:
|
||||||
|
{type: "uid", uid: string} |
|
||||||
|
{type: "room", room: string} |
|
||||||
|
{type: "group", group: string},
|
||||||
|
title: string,
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
notifRouter.post("/send", async (req: Request<undefined, PushResult, PushSendBody>, res: Response<PushResult>) => {
|
||||||
|
let recp: string
|
||||||
|
switch (req.body.recp.type) {
|
||||||
|
case "uid":
|
||||||
|
recp = req.body.recp.uid
|
||||||
|
break;
|
||||||
|
case "room":
|
||||||
|
recp = req.body.recp.room
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
if (!capability.settings.groups) return res.sendStatus(406).end()
|
||||||
|
recp = req.body.recp.group
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.status(400).end()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const message = new Message(req.body.title, req.body.body, req.body.recp.type, recp)
|
||||||
|
let result: PushResult = await message.send()
|
||||||
|
console.log(`
|
||||||
|
From: ${req.user.uname} (${req.user._id})
|
||||||
|
To: ${recp}
|
||||||
|
Subject: ${req.body.title}
|
||||||
|
|
||||||
|
${req.body.body}
|
||||||
|
`);
|
||||||
|
res.send(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
notifRouter.get("/groups", async (req, res) => {
|
||||||
|
res.send(await Group.find({}, { name: 1, _id: 1 }))
|
||||||
|
})
|
||||||
|
|
||||||
|
notifRouter.use("/outbox", outboxRouter)
|
||||||
|
|
||||||
|
export { notifRouter }
|
||||||
35
src/routes/api/admin/notif/outbox.ts
Normal file
35
src/routes/api/admin/notif/outbox.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Inbox from "@/schemas/Inbox";
|
||||||
|
import { IUser } from "@/schemas/User";
|
||||||
|
import { Response, Router } from "express";
|
||||||
|
|
||||||
|
export const outboxRouter = Router()
|
||||||
|
|
||||||
|
outboxRouter.get("/", async (req, res: Response) => {
|
||||||
|
var result = await Inbox.find({}, {message: 1, sentDate: 1}, {sort: {sentDate: -1}})
|
||||||
|
var final = result.map(v => {
|
||||||
|
return {
|
||||||
|
_id: v._id,
|
||||||
|
sentDate: v.sentDate,
|
||||||
|
title: v.message.title
|
||||||
|
}
|
||||||
|
})
|
||||||
|
res.send(final)
|
||||||
|
})
|
||||||
|
|
||||||
|
outboxRouter.get("/:id/message", async (req, res) => {
|
||||||
|
var msg = await Inbox.findById(req.params.id, {message: 1})
|
||||||
|
if (msg) {
|
||||||
|
res.send(msg.message.body)
|
||||||
|
} else {
|
||||||
|
res.status(404).send({message: "ERR: 404 Message id not found"})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
outboxRouter.get("/:id/rcpts", async (req, res) => {
|
||||||
|
var msg = await Inbox.findById(req.params.id, {rcpt: 1}).populate<{rcpt: Pick<IUser, "uname" | "room" | "fname" | "surname">}>({path: "rcpt", select: ["uname", "room", "fname", "surname"]}).exec()
|
||||||
|
if (msg) {
|
||||||
|
res.send(msg.rcpt)
|
||||||
|
} else {
|
||||||
|
res.status(404).send({message: "ERR: 404 Message id not found"})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import { adminPerm, Perms, project } from "@/utility";
|
import { adminPerm, Perms } from "@/utility";
|
||||||
import usettings from "@/usettings";
|
import usettings from "@/helpers/usettings";
|
||||||
|
|
||||||
export const settingsRouter = Router()
|
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 = project(req.body, {keyrooms: true, cleanThings: true, rooms: true, menu: true})
|
usettings.value = req.body
|
||||||
res.send({status: 200})
|
res.send({status: 200})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
import { islogged, isadmin} from "@/utility";
|
|
||||||
import { newsRouter } from "./admin/news";
|
|
||||||
import { accsRouter } from "./admin/accs";
|
|
||||||
import { menuRouter } from "./admin/menu";
|
|
||||||
import { groupsRouter } from "./admin/groups";
|
|
||||||
import { notifRouter } from "./admin/notif";
|
|
||||||
import { keysRouter } from "./admin/keys";
|
|
||||||
import { cleanRouter } from "./admin/clean";
|
|
||||||
import { settingsRouter } from "./admin/settings";
|
|
||||||
|
|
||||||
const adminRouter = Router()
|
|
||||||
|
|
||||||
adminRouter.use(islogged, isadmin)
|
|
||||||
adminRouter.use('/news', newsRouter)
|
|
||||||
adminRouter.use('/accs', accsRouter)
|
|
||||||
adminRouter.use('/menu', menuRouter)
|
|
||||||
adminRouter.use('/groups', groupsRouter)
|
|
||||||
adminRouter.use('/notif', notifRouter)
|
|
||||||
adminRouter.use('/keys', keysRouter)
|
|
||||||
adminRouter.use('/clean', cleanRouter)
|
|
||||||
adminRouter.use('/settings', settingsRouter)
|
|
||||||
|
|
||||||
adminRouter.get('/usearch', (req, res) => {
|
|
||||||
// TODO: Add search
|
|
||||||
res.send([req.query['q']])
|
|
||||||
})
|
|
||||||
|
|
||||||
export {adminRouter};
|
|
||||||
@@ -4,17 +4,20 @@ import News from "@schemas/News";
|
|||||||
import Menu from "@schemas/Menu";
|
import Menu from "@schemas/Menu";
|
||||||
import Vote from "@schemas/Vote";
|
import Vote from "@schemas/Vote";
|
||||||
import { vote } from "@/pipelines/vote";
|
import { vote } from "@/pipelines/vote";
|
||||||
import capability, { Features } from "@/capability";
|
import capability, { Features } from "@/helpers/capability";
|
||||||
import Key from "@schemas/Key";
|
import Key, { IKey } from "@schemas/Key";
|
||||||
import usettings from "@/usettings";
|
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";
|
||||||
const appRouter = Router();
|
import Inbox from "@/schemas/Inbox";
|
||||||
|
import { IUser } from "@/schemas/User";
|
||||||
|
|
||||||
|
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -26,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
|
||||||
@@ -58,9 +61,9 @@ 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({tb: {$exists: false}}, {_id: 0, room: 1, whom: 0}, {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 }
|
||||||
})
|
})
|
||||||
@@ -75,4 +78,26 @@ appRouter.get("/clean/:date", capability.mw(Features.Clean), async (req, res) =>
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
export {appRouter};
|
appRouter.get("/notif/check", capability.mw(Features.Notif), async (req, res) => {
|
||||||
|
var result = await Inbox.find({rcpt: req.user._id, $nor: [{ack: req.user._id}]}, {message: 1, sentDate: 1})
|
||||||
|
if (result) {
|
||||||
|
res.send(result)
|
||||||
|
} else {
|
||||||
|
res.send([])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
appRouter.post("/notif/:id/ack", capability.mw(Features.Notif), async (req, res) => {
|
||||||
|
var result = await Inbox.findById(req.params.id)
|
||||||
|
if (result) {
|
||||||
|
if (result.rcpt.includes(req.user._id) && !result.ack.includes(req.user._id)) {
|
||||||
|
result.ack.push(req.user._id)
|
||||||
|
await result.save({})
|
||||||
|
res.send({status: 200})
|
||||||
|
} else {
|
||||||
|
res.status(403).send({status: 401, message: "User doesn't have access or message already acknowledged"})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404).send({status: 404, message: "Message not found"})
|
||||||
|
}
|
||||||
|
})
|
||||||
8
src/routes/api/index.ts
Normal file
8
src/routes/api/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { appRouter } from "./app";
|
||||||
|
import { adminRouter } from "./admin";
|
||||||
|
|
||||||
|
export const apiRouter = Router();
|
||||||
|
|
||||||
|
apiRouter.use("/app", appRouter)
|
||||||
|
apiRouter.use("/admin", adminRouter)
|
||||||
@@ -3,16 +3,44 @@ import passport from "passport";
|
|||||||
import User from "@schemas/User";
|
import User from "@schemas/User";
|
||||||
import { islogged } from "@/utility";
|
import { islogged } from "@/utility";
|
||||||
import bcrypt from "bcryptjs"
|
import bcrypt from "bcryptjs"
|
||||||
import cap from "@/capability";
|
import cap from "@/helpers/capability";
|
||||||
import usettings from "@/usettings";
|
import usettings from "@/helpers/usettings";
|
||||||
import { readFileSync } from "node:fs";
|
|
||||||
import vapidKeys from "@/vapidKeys";
|
import vapidKeys from "@/vapidKeys";
|
||||||
|
import { IVerifyOptions } from "passport-local";
|
||||||
|
|
||||||
const authRouter = Router()
|
const authRouter = Router()
|
||||||
|
|
||||||
authRouter.post("/login", passport.authenticate('normal'), (req, res) => {
|
authRouter.post("/login", (req, res) => {
|
||||||
if (req.user.admin != null) res.send({status: 200, admin: req.user.admin})
|
passport.authenticate('normal', (err: {type: string, message: string} | null, user?: Express.User | false, options?: IVerifyOptions) => {
|
||||||
else res.send({status: 200})
|
if (user) {
|
||||||
|
req.login(user, (error) => {
|
||||||
|
if (error) {
|
||||||
|
res.status(500).send(error)
|
||||||
|
} else {
|
||||||
|
res.send({status: 200, admin: req.user.admin || undefined, redirect: req.user.defaultPage})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (err) {
|
||||||
|
switch (err.type) {
|
||||||
|
case "unf":
|
||||||
|
res.status(404).send({status: 404, message: "Zła nazwa użytkownika lub hasło."})
|
||||||
|
break;
|
||||||
|
case "timeout":
|
||||||
|
res.status(403).send({status: 403, message: err.message})
|
||||||
|
break;
|
||||||
|
case "locked":
|
||||||
|
res.status(403).send({status: 403, message: err.message})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.status(500).send({status: 500, message: err.message})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(403).send({status: 403, message: "Brak hasła lub loginu."})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(req, res)
|
||||||
})
|
})
|
||||||
|
|
||||||
authRouter.post("/chpass", islogged, async (req,res) => {
|
authRouter.post("/chpass", islogged, async (req,res) => {
|
||||||
@@ -51,10 +79,20 @@ authRouter.get("/check", islogged, (req, res, next) => {
|
|||||||
if (req.user.locked) {
|
if (req.user.locked) {
|
||||||
req.logout((err) => {
|
req.logout((err) => {
|
||||||
if (err) next(err)
|
if (err) next(err)
|
||||||
res.status(401).send("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) => {
|
||||||
|
if (["", "/", "/login", "/login/", "login"].find(v => v == req.body.redirect)) return res.status(400).send({status: 400, message: "Path in blacklist"})
|
||||||
|
const update = await User.findByIdAndUpdate(req.user._id, {defaultPage: req.body.redirect})
|
||||||
|
if (update) {
|
||||||
|
res.send({status: 200}).end()
|
||||||
|
} else {
|
||||||
|
res.status(500).send({status: 500}).end()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export { authRouter };
|
export { authRouter };
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import Notification from "@schemas/Notification";
|
import Notification from "@schemas/Notification";
|
||||||
import { islogged } from "@/utility";
|
import { islogged } from "@/utility";
|
||||||
import { adminRouter } from "./api/adminRouter";
|
|
||||||
import { appRouter } from "./api/appRouter";
|
|
||||||
import { authRouter } from "./auth/index";
|
import { authRouter } from "./auth/index";
|
||||||
import { Schema } from 'mongoose'
|
import capability, { Features } from "@/helpers/capability";
|
||||||
import capability, { Features } from "@/capability";
|
import mongoose from "mongoose";
|
||||||
|
import { apiRouter } from "./api";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/app', appRouter)
|
router.use('/', apiRouter)
|
||||||
router.use('/admin', adminRouter)
|
|
||||||
router.use('/auth', authRouter)
|
router.use('/auth', authRouter)
|
||||||
|
|
||||||
|
router.get("/healthcheck", async (req, res) => {
|
||||||
|
res.status(200).send({
|
||||||
|
uptime: process.uptime(),
|
||||||
|
date: new Date(),
|
||||||
|
db: mongoose.connection.readyState
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
router.post("/notif", islogged, capability.mw(Features.Notif), async (req, res) => {
|
router.post("/notif", islogged, capability.mw(Features.Notif), async (req, res) => {
|
||||||
var obj = {user: req.user._id, ...req.body}
|
var obj = {user: req.user._id, ...req.body}
|
||||||
await Notification.findOneAndUpdate(obj, obj, {upsert: true})
|
await Notification.findOneAndUpdate(obj, obj, {upsert: true})
|
||||||
res.send({"status": 200})
|
res.send({"status": 200})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.use("/", apiRouter)
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
18
src/schemas/Inbox.ts
Normal file
18
src/schemas/Inbox.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { SimpleMessage } from "@/notif"
|
||||||
|
import mongoose, { Types, Schema } from "mongoose"
|
||||||
|
|
||||||
|
export interface IInbox {
|
||||||
|
message: SimpleMessage,
|
||||||
|
sentDate: Date,
|
||||||
|
rcpt: Types.ObjectId[],
|
||||||
|
ack: Types.ObjectId[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const inboxSchema = new Schema<IInbox>({
|
||||||
|
message: {type: Object, required: true},
|
||||||
|
sentDate: {type: Date, required: true, default: Date.now()},
|
||||||
|
rcpt: [{type: Schema.Types.ObjectId, ref: "logins", required: true}],
|
||||||
|
ack: [{type: Schema.Types.ObjectId, ref: "logins", required: true, default: []}],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mongoose.model("inbox", inboxSchema)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Schema, Types, model } from "mongoose"
|
import { Schema, Types, model } from "mongoose"
|
||||||
|
|
||||||
interface IKey {
|
export interface IKey {
|
||||||
room: string;
|
room: string;
|
||||||
whom: Types.ObjectId;
|
whom: Types.ObjectId;
|
||||||
borrow: Date;
|
borrow: Date;
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -1,27 +1,32 @@
|
|||||||
|
import { Perms } from "@/utility";
|
||||||
import mongoose, { Types, Schema } from "mongoose"
|
import mongoose, { Types, Schema } from "mongoose"
|
||||||
|
|
||||||
// TODO: Unify `fname` and `surename` into single field
|
|
||||||
|
|
||||||
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;
|
||||||
groups: Types.ObjectId[];
|
groups: Types.ObjectId[];
|
||||||
|
regDate: Date;
|
||||||
|
defaultPage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userSchema = new Schema<IUser>({
|
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: String,
|
room: {type: String, default: ""},
|
||||||
admin: Number,
|
admin: [{type: String}],
|
||||||
locked: {type: Boolean, default: false},
|
locked: {type: Boolean, default: false},
|
||||||
fname: String,
|
fname: {type: String, default: ""},
|
||||||
surname: String,
|
surname: {type: String, default: ""},
|
||||||
groups: [{type: mongoose.Types.ObjectId, ref: "Group"}]
|
groups: [{type: mongoose.Types.ObjectId, ref: "Group"}],
|
||||||
|
regDate: {type: Date, default: Date.now},
|
||||||
|
defaultPage: {type: String, default: ""},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
userSchema.index({uname: "text", room: "text", fname: "text", surname: "text"}, {weights: {fname: 3, surname: 4, room: 2, uname: 1}, default_language: "none"})
|
||||||
|
|
||||||
export default mongoose.model("logins", userSchema)
|
export default mongoose.model("logins", userSchema)
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { readFileSync, writeFileSync } from "node:fs";
|
|
||||||
interface IUSettings {
|
|
||||||
keyrooms: string[];
|
|
||||||
rooms: string[];
|
|
||||||
cleanThings: string[];
|
|
||||||
menu: {
|
|
||||||
defaultItems: {
|
|
||||||
sn: string[];
|
|
||||||
kol: string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UOptions {
|
|
||||||
private _settings: IUSettings;
|
|
||||||
public get settings(): IUSettings {
|
|
||||||
return this._settings;
|
|
||||||
}
|
|
||||||
public set settings(value: IUSettings) {
|
|
||||||
this._settings = value;
|
|
||||||
this.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
private save() {
|
|
||||||
writeFileSync("./config/usettings.json", JSON.stringify(this._settings, undefined, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
reload() {
|
|
||||||
this.settings = JSON.parse(readFileSync("./config/usettings.json", {encoding: "utf-8"}))
|
|
||||||
console.log("Reloaded user settings");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new UOptions();
|
|
||||||
@@ -8,21 +8,21 @@ var islogged = (req: Request, res: Response, next: NextFunction) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isadmin = (req: Request, res: Response, next: NextFunction) => {
|
var isadmin = (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (req.user.admin != null) {
|
if (req.user.admin) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
res.sendStatus(401)
|
res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,16 +34,26 @@ var adminPerm = (perm: Perms) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var adminCond = (adminInt = 0, perm: Perms) => {
|
var adminCond = (perms: Perms[], perm: Perms) => {
|
||||||
return (adminInt & perm) == perm
|
return perms.includes(perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
var project = (obj: any, projection: any) => {
|
export function project<T extends object>(obj: T | any, projection?: (keyof T)[] | { [key in keyof T]: any}): Partial<T> {
|
||||||
let obj2: any = {}
|
let obj2: Partial<T> = {}
|
||||||
for (let key in projection) {
|
if (projection) {
|
||||||
if (key in obj) obj2[key] = obj[key]
|
if (projection instanceof Array) {
|
||||||
|
for (let key of projection) {
|
||||||
|
if (key in obj) obj2[key] = obj[key]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let key in projection) {
|
||||||
|
if (key in obj) obj2[key] = obj[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj2
|
||||||
|
} else {
|
||||||
|
return obj
|
||||||
}
|
}
|
||||||
return obj2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {islogged, isadmin, adminPerm, Perms, adminCond, project};
|
export {islogged, isadmin, adminPerm, Perms, adminCond};
|
||||||
@@ -20,11 +20,22 @@ class VapidKeysSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this._keys = JSON.parse(readFileSync("./config/keys.json", {encoding: "utf-8"}))
|
try {
|
||||||
if (!(this._keys.privateKey && this._keys.publicKey)) {
|
this._keys = JSON.parse(readFileSync("./config/keys.json", {encoding: "utf-8"}))
|
||||||
this.keys = generateVAPIDKeys()
|
} 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()
|
||||||
|
}
|
||||||
|
console.log("Reloaded VAPID keys");
|
||||||
}
|
}
|
||||||
console.log("Reloaded VAPID keys");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user