feat: Added notifications outbox to admin panel
This commit is contained in:
107
src/notif.ts
107
src/notif.ts
@@ -1,11 +1,27 @@
|
||||
import { PushSubscription, RequestOptions, VapidKeys, WebPushError, sendNotification } from "web-push";
|
||||
import { RequestOptions, SendResult, VapidKeys, WebPushError, sendNotification } from "web-push";
|
||||
import Notification from "./schemas/Notification";
|
||||
import vapidKeys from "./vapidKeys";
|
||||
import { IUser } from "./schemas/User";
|
||||
import User, { IUser } from "./schemas/User";
|
||||
import Inbox from "./schemas/Inbox";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export class NotifcationHelper {
|
||||
export interface SimpleMessage {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface PushResult {
|
||||
sent: number;
|
||||
possible: number;
|
||||
}
|
||||
|
||||
export class Message {
|
||||
private options: RequestOptions
|
||||
constructor () {
|
||||
private message: { notification: SimpleMessage }
|
||||
private rcptType: "uname" | "room" | "group"
|
||||
private rcpt: string
|
||||
|
||||
constructor (title: string, body: string, rcptType: "uname" | "room" | "group", rcpt: string) {
|
||||
let keys: VapidKeys = vapidKeys.keys
|
||||
this.options = {
|
||||
vapidDetails: {
|
||||
@@ -14,27 +30,66 @@ export class NotifcationHelper {
|
||||
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(uname: string) {
|
||||
var notif = await Notification.find().populate<{user: Pick<IUser, 'uname'> & {_id: Types.ObjectId}}>('user', ['uname', '_id']).exec()
|
||||
return notif.filter(val => val.user.uname == uname)
|
||||
}
|
||||
|
||||
async findRoomNotif(room: string) {
|
||||
var notif = await Notification.find().populate<{user: Pick<IUser, 'room'> & {_id: Types.ObjectId}}>('user', ['room', '_id']).exec()
|
||||
return notif.filter(val => val.user.room == room)
|
||||
}
|
||||
|
||||
async findGroupNotif(groupId: string) {
|
||||
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))
|
||||
}
|
||||
|
||||
public async send(): Promise<PushResult> {
|
||||
var subscriptions
|
||||
var rcptIds: Types.ObjectId[]
|
||||
switch (this.rcptType) {
|
||||
case "uname":
|
||||
subscriptions = await this.findUserNotif(this.rcpt)
|
||||
rcptIds = (await User.find({uname: this.rcpt})).map(v => v._id)
|
||||
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
|
||||
var result: SendResult
|
||||
try {
|
||||
result = await sendNotification(v, message, this.options)
|
||||
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.findOneAndDelete({endpoint: v.endpoint, keys: v.keys})
|
||||
await Notification.findByIdAndRemove(v._id)
|
||||
subslen--
|
||||
break;
|
||||
case 404:
|
||||
console.warn("NOT FOUND", error.message)
|
||||
await Notification.findOneAndDelete(v)
|
||||
await Notification.findByIdAndRemove(v._id)
|
||||
subslen--
|
||||
break;
|
||||
default:
|
||||
@@ -44,39 +99,7 @@ export class NotifcationHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {sent: count, possible: subslen}
|
||||
}
|
||||
|
||||
private rcpt(message: string) {
|
||||
return {
|
||||
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)
|
||||
}
|
||||
|
||||
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,38 +1,44 @@
|
||||
import { Router } from "express";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { Perms, adminPerm } from "@/utility";
|
||||
import Group from "@schemas/Group";
|
||||
import { NotifcationHelper } from "@/notif";
|
||||
import { PushResult, Message } from "@/notif";
|
||||
import capability, { Features } from "@/helpers/capability";
|
||||
import Inbox from "@/schemas/Inbox";
|
||||
import { Types } from "mongoose";
|
||||
import { IUser } from "@/schemas/User";
|
||||
|
||||
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)
|
||||
type PushSendBody = {recp:
|
||||
{type: "uname", uname: 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
|
||||
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;
|
||||
}
|
||||
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}
|
||||
@@ -43,6 +49,17 @@ notifRouter.post("/send", async (req, res) => {
|
||||
res.send(result)
|
||||
})
|
||||
|
||||
notifRouter.get("/outbox", async (req, res: Response) => {
|
||||
var result = await Inbox.find({}, {}, {sort: {sentDate: -1}}).populate<{rcpt: IUser & {_id: Types.ObjectId}}>("rcpt", ['fname', 'surname', 'uname', '_id', 'room']).exec()
|
||||
var final = result.map(v => {
|
||||
return {
|
||||
...v.toJSON(),
|
||||
ack: v.ack.length
|
||||
}
|
||||
})
|
||||
res.send(final)
|
||||
})
|
||||
|
||||
notifRouter.get("/groups", async (req, res) => {
|
||||
res.send(await Group.find({}, { name: 1, _id: 1 }))
|
||||
})
|
||||
|
||||
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,7 +1,5 @@
|
||||
import mongoose, { Types, Schema } from "mongoose"
|
||||
|
||||
// TODO: Unify `fname` and `surename` into single field
|
||||
|
||||
export interface IUser {
|
||||
uname: string;
|
||||
pass: string;
|
||||
|
||||
Reference in New Issue
Block a user