Notifications (#1)
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
34
src/notif.ts
34
src/notif.ts
@@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 }
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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>({
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ interface IUSettings {
|
|||||||
sn: string[];
|
sn: string[];
|
||||||
kol: string[];
|
kol: string[];
|
||||||
}
|
}
|
||||||
},
|
|
||||||
vapid: {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
src/vapidKeys.ts
Normal file
31
src/vapidKeys.ts
Normal 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();
|
||||||
Reference in New Issue
Block a user