Notifications (#1)

This commit is contained in:
2025-05-09 12:46:53 +02:00
committed by GitHub
parent 9423b4804a
commit 5d1c0bc2ba
12 changed files with 73 additions and 122 deletions

View File

@@ -35,7 +35,7 @@ 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", `htt[s://${process.env.DOMAIN}`,], origin: ["http://localhost:4200", "http://localhost:3000", `https://${process.env.DOMAIN}`,],
credentials: true credentials: true
})) }))
app.use(session({ app.use(session({

View File

@@ -1,15 +1,16 @@
import { PushSubscription, RequestOptions, SendResult, sendNotification } from "web-push"; import { PushSubscription, RequestOptions, VapidKeys, sendNotification } from "web-push";
import { readFileSync } from "fs";
import Notification from "./schemas/Notification"; import Notification from "./schemas/Notification";
import { allNotif, groupNotif, roomNotif, userNotif } from "./pipelines/notif"; import vapidKeys from "./vapidKeys";
import { Types } from "mongoose";
import { IUser } from "./schemas/User";
export class NotifcationHelper { export class NotifcationHelper {
private options: RequestOptions private options: RequestOptions
constructor () { constructor () {
let keys = JSON.parse(readFileSync("./config/keys.json", 'utf-8')) let keys: VapidKeys = vapidKeys.keys
this.options = { this.options = {
vapidDetails: { vapidDetails: {
subject: "CHANGE ME", subject: `https://${process.env.DOMAIN}`,
privateKey: keys.privateKey, privateKey: keys.privateKey,
publicKey: keys.publicKey publicKey: keys.publicKey
} }
@@ -39,16 +40,13 @@ export class NotifcationHelper {
private rcpt(message: string) { private rcpt(message: string) {
return { return {
user: async (uname: string) => { user: async (uname: string) => {
return await this.send(message, await Notification.aggregate(userNotif(uname))) return await this.send(message, await this.findUserNotif(uname))
}, },
room: async (room: string) => { room: async (room: string) => {
return await this.send(message, await Notification.aggregate(roomNotif(room))) return await this.send(message, await this.findRoomNotif(room))
}, },
group: async (group: string) => { group: async (group: string) => {
return await this.send(message, []) return await this.send(message, await this.findGroupNotif(group))
},
withRoom: async () => {
return await this.send(message, await Notification.aggregate(allNotif()))
} }
} }
} }
@@ -57,4 +55,18 @@ export class NotifcationHelper {
return this.rcpt(JSON.stringify({notification: {title: title, body: body}})) 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)
}
async findGroupNotif(groupId: string): Promise<Array<any>> {
var notif = await Notification.find().populate<{user: Pick<IUser, 'groups'>}>('user', 'groups').exec()
return notif.filter(val => val.user.groups.find(x => x.toString() == groupId))
}
} }

View File

@@ -1,88 +0,0 @@
import { PipelineStage, Types } from "mongoose"
function userNotif(uname: string) {
var pipeline: PipelineStage[] = [
{
"$match": {
uname: uname
}
}
]
return pipeline
}
function roomNotif(room: string) {
var pipeline: PipelineStage[] = [
{
$lookup: {
from: "logins",
localField: "uname",
foreignField: "uname",
as: "user",
},
},
{
$unwind: {
path: "$user",
preserveNullAndEmptyArrays: false,
},
},
{
$addFields: {
rooms: "$user.room",
},
},
{
$match:
{
rooms: room,
},
},
{
$unset:
["user", "uname", "rooms"],
},
]
return pipeline
}
function allNotif() {
var pipeline: PipelineStage[] = [
{
$lookup: {
from: "logins",
localField: "uname",
foreignField: "uname",
as: "user",
},
},
{
$unwind: {
path: "$user",
preserveNullAndEmptyArrays: false,
},
},
{
$addFields: {
rooms: "$user.room",
},
},
{
$match:
{
rooms: {$exists: true},
},
},
{
$unset:
["user", "uname", "rooms"],
},
]
return pipeline
}
function groupNotif(group: string) {
return
}
export { userNotif, roomNotif, allNotif, groupNotif }

View File

@@ -13,24 +13,20 @@ notifRouter.use(capability.mw(Features.Notif))
notifRouter.post("/send", async (req, res) => { notifRouter.post("/send", async (req, res) => {
const message = nh.simpleMessage(req.body.title, req.body.body) const message = nh.simpleMessage(req.body.title, req.body.body)
let recp: string | number let recp: string
let result; let result;
switch (req.body.recp.type) { switch (req.body.recp.type) {
case "uname": case "uname":
recp = req.body.recp.uname as string recp = req.body.recp.uname
result = await message.user(recp); result = await message.user(recp);
break; break;
case "room": case "room":
recp = req.body.recp.room as string recp = req.body.recp.room
result = await message.room(recp) result = await message.room(recp)
break; break;
case "all":
recp = "all"
result = await message.withRoom()
break;
case "group": case "group":
if (!capability.settings.groups) return res.sendStatus(406).end() if (!capability.settings.groups) return res.sendStatus(406).end()
recp = req.body.recp.group as string recp = req.body.recp.group
result = await message.group(recp) result = await message.group(recp)
break; break;
default: default:

View File

@@ -5,6 +5,8 @@ import { islogged } from "@/utility";
import bcrypt from "bcryptjs" import bcrypt from "bcryptjs"
import cap from "@/capability"; import cap from "@/capability";
import usettings from "@/usettings"; import usettings from "@/usettings";
import { readFileSync } from "node:fs";
import vapidKeys from "@/vapidKeys";
const authRouter = Router() const authRouter = Router()
@@ -52,7 +54,7 @@ authRouter.get("/check", islogged, (req, res, next) => {
res.status(401).send("Your account has been locked.") res.status(401).send("Your account has been locked.")
}) })
} }
res.send({"admin": req.user.admin, "features": cap.flags, "vapid": usettings.settings.vapid, "room": req.user.room, "menu": {"defaultItems": usettings.settings.menu.defaultItems}}) res.send({"admin": req.user.admin, "features": cap.flags, "room": req.user.room, "menu": {"defaultItems": usettings.settings.menu.defaultItems}, "vapid": vapidKeys.keys.publicKey})
}) })
export { authRouter }; export { authRouter };

View File

@@ -4,6 +4,7 @@ import { islogged } from "@/utility";
import { adminRouter } from "./api/adminRouter"; import { adminRouter } from "./api/adminRouter";
import { appRouter } from "./api/appRouter"; import { appRouter } from "./api/appRouter";
import { authRouter } from "./auth/index"; import { authRouter } from "./auth/index";
import { Schema } from 'mongoose'
import capability, { Features } from "@/capability"; import capability, { Features } from "@/capability";
const router = Router(); const router = Router();
@@ -13,7 +14,7 @@ router.use('/admin', adminRouter)
router.use('/auth', authRouter) router.use('/auth', authRouter)
router.post("/notif", islogged, capability.mw(Features.Notif), async (req, res) => { router.post("/notif", islogged, capability.mw(Features.Notif), async (req, res) => {
var obj = {uname: req.user.uname, ...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})
}) })

View File

@@ -1,4 +1,4 @@
import { ObjectId, Schema, model } from "mongoose" import { Schema, model } from "mongoose"
export interface GradeNote { export interface GradeNote {
label: string; label: string;

View File

@@ -1,8 +1,8 @@
import { ObjectId, Schema, model } from "mongoose" import { Schema, Types, model } from "mongoose"
interface IKey { interface IKey {
room: string; room: string;
whom: ObjectId; whom: Types.ObjectId;
borrow: Date; borrow: Date;
tb?: Date; tb?: Date;
} }

View File

@@ -1,4 +1,4 @@
import mongoose, { Schema } from "mongoose" import mongoose, { Types, Schema } from "mongoose"
interface INotification { interface INotification {
endpoint: string; endpoint: string;
@@ -6,7 +6,7 @@ interface INotification {
auth: string; auth: string;
p256dh: string; p256dh: string;
}; };
uname: string user: Types.ObjectId
expirationTime?: number expirationTime?: number
} }
@@ -16,7 +16,7 @@ const notifSchema = new Schema<INotification>({
auth: String, auth: String,
p256dh: String, p256dh: String,
}, },
uname: {type: String, required: true}, user: {type: Schema.ObjectId, required: true, ref: "logins"},
expirationTime: Number expirationTime: Number
}) })

View File

@@ -1,8 +1,8 @@
import mongoose, { ObjectId, Schema } from "mongoose" import mongoose, { Types, Schema } from "mongoose"
// TODO: Unify `fname` and `surename` into single field // TODO: Unify `fname` and `surename` into single field
interface IUser { export interface IUser {
uname: string; uname: string;
pass: string; pass: string;
room?: string; room?: string;
@@ -10,7 +10,7 @@ interface IUser {
locked?: boolean; locked?: boolean;
fname?: string; fname?: string;
surname?: string; surname?: string;
groups: ObjectId[]; groups: Types.ObjectId[];
} }
const userSchema = new Schema<IUser>({ const userSchema = new Schema<IUser>({

View File

@@ -8,9 +8,6 @@ interface IUSettings {
sn: string[]; sn: string[];
kol: string[]; kol: string[];
} }
},
vapid: {
} }
} }

31
src/vapidKeys.ts Normal file
View File

@@ -0,0 +1,31 @@
import { readFileSync, writeFileSync } from "node:fs";
import { generateVAPIDKeys, VapidKeys } from "web-push";
class VapidKeysSettings {
private _keys: VapidKeys;
public get keys(): VapidKeys {
return this._keys;
}
public set keys(value: VapidKeys) {
this._keys = value;
this.save()
}
constructor() {
this.reload()
}
private save() {
writeFileSync("./config/keys.json", JSON.stringify(this._keys, undefined, 2))
}
reload() {
this._keys = JSON.parse(readFileSync("./config/keys.json", {encoding: "utf-8"}))
if (!(this._keys.privateKey && this._keys.publicKey)) {
this.keys = generateVAPIDKeys()
}
console.log("Reloaded VAPID keys");
}
}
export default new VapidKeysSettings();