Notification system enhanced

- Base for notifcation center
- Show text in map if no data is available
This commit is contained in:
2020-11-21 14:11:26 +01:00
parent 8b54431449
commit 533749c7c8
26 changed files with 323 additions and 76 deletions

View File

@@ -10,6 +10,7 @@ import * as winston from 'winston';
import { config } from './config';
import { GetBeat, GetBeatStats } from './endpoints/beat';
import { getNotification } from './endpoints/notification';
import { GetPhone, PostPhone } from './endpoints/phone';
import { DeleteUser, GetUser, LoginRabbitUser, LoginUser, MW_User, PatchUser, PostUser, Resource, Topic, VHost } from './endpoints/user';
import { hashPassword, randomPepper, randomString } from './lib/crypto';
@@ -145,17 +146,20 @@ async function run() {
app.get('/user/resource', (req, res) => Resource(req, res));
app.get('/user/topic', (req, res) => Topic(req, res));
// Basic user actions
// CRUD user
app.get('/user/notification', MW_User, (req, res) => getNotification(req, res)); // Notifications
app.get('/user/', MW_User, (req, res) => GetUser(req, res));
app.post('/user/', MW_User, (req, res) => PostUser(req, res));
app.get('/user/:id', MW_User, (req, res) => GetUser(req, res));
app.patch('/user/:id', MW_User, (req, res) => PatchUser(req, res));
app.delete('/user/:id', MW_User, (req, res) => DeleteUser(req, res));
// Phones
app.get('/phone/:id', MW_User, (req, res) => GetPhone(req, res));
app.get('/phone', MW_User, (req, res) => GetPhone(req, res));
app.post('/phone', MW_User, (req, res) => PostPhone(req, res));
// Beats
app.get('/beat/', MW_User, (req, res) => GetBeat(req, res));
app.get('/beat/stats', MW_User, (req, res) => GetBeatStats(req, res));

View File

@@ -0,0 +1,22 @@
import { Response } from "express";
import { LivebeatRequest } from "../lib/request";
import { Notification } from "../models/notifications/notification.model";
export async function getNotification(req: LivebeatRequest, res: Response) {
let limit = req.query.limit;
let skip = req.query.skip;
if (limit === undefined) { limit = '100' };
if (skip === undefined) { skip = '0' };
try {
const notifications = await Notification.find({ user: req.user?._id }).limit(Number(limit)).sort({ _id: -1 });
res.status(200).send(notifications);
} catch(error: any) {
if (error instanceof TypeError) {
res.status(400).send({ message: "'Limit' has to be a number" });
} else {
res.status(500).send({ message: "An error occured while processing your request" });
}
}
}

View File

@@ -1,7 +1,12 @@
import * as amqp from 'amqplib';
import { Schema, SchemaType } from 'mongoose';
import { logger, RABBITMQ_URI } from '../app';
import { Beat } from '../models/beat/beat.model.';
import { ISeverity, NotificationType } from '../models/notifications/notification.interface';
import { addNotification, Notification } from '../models/notifications/notification.model';
import { IPhone } from '../models/phone/phone.interface';
import { Phone } from '../models/phone/phone.model';
import { User } from '../models/user/user.model';
interface IBeat {
token: string,
@@ -14,7 +19,7 @@ export class RabbitMQ {
connection: amqp.Connection | null = null;
channel: amqp.Channel | null = null;
timeouts: Map<string, Timeout> = new Map<string, Timeout>();
timeouts: Map<string, NodeJS.Timeout> = new Map<string, NodeJS.Timeout>();
async init() {
this.connection = await amqp.connect(RABBITMQ_URI);
@@ -46,16 +51,15 @@ export class RabbitMQ {
// Broadcast if device became active
if (this.timeouts.has(phone.id)) {
clearTimeout(this.timeouts.get(phone.id));
clearTimeout(this.timeouts.get(phone.id)!!);
} else {
logger.debug('Set phone active');
phone.active = true;
await phone.save();
this.publish(phone.user.toString(), phone.toJSON(), 'phone_alive');
this.publish(phone.user.toString(), phone.toJSON(), 'phone_alive', ISeverity.SUCCESS);
}
const timeoutTimer = setTimeout(async () => {
this.publish(phone.user.toString(), phone.toJSON(), 'phone_dead');
this.publish(phone.user.toString(), phone.toJSON(), 'phone_dead', ISeverity.WARN);
this.timeouts.delete(phone.id);
phone.active = false;
await phone.save();
@@ -66,10 +70,22 @@ export class RabbitMQ {
}, { noAck: true });
}
async publish(userId: string, data: any, type: 'beat' | 'phone_alive' | 'phone_dead' | 'phone_register' | 'panic' = 'beat') {
if (this.connection == undefined) await this.init()
async publish(userId: string, data: any, type: NotificationType, severity = ISeverity.INFO) {
if (this.connection == undefined) await this.init();
data = { type, ...data };
const user = await User.findById(userId);
if (user === null) return;
/* Manage notifications */
if (type != 'beat') {
if (type == 'phone_alive' || type == 'phone_dead') {
addNotification(type, severity, ((data as IPhone)._id), user);
}
}
data = { type, severity, ...data };
console.log('Send:', data);
this.channel?.publish('amq.topic', userId, Buffer.from(JSON.stringify(data)));
}
}

View File

@@ -6,7 +6,7 @@ const schemaBeat = new Schema({
accuracy: { type: Number, required: false },
speed: { type: Number, required: false },
battery: { type: Number, required: false },
phone: { type: SchemaTypes.ObjectId, required: true, default: 'user' },
phone: { type: SchemaTypes.ObjectId, required: true },
createdAt: { type: SchemaTypes.Date, required: false }
}, {
timestamps: {

View File

@@ -0,0 +1,18 @@
import { Document } from "mongoose";
import { IUser } from "../user/user.interface";
export enum ISeverity {
INFO = 0,
SUCCESS = 1,
WARN = 2,
ERROR = 3
}
export type NotificationType = 'beat' | 'phone_alive' | 'phone_dead' | 'phone_register' | 'panic';
export interface INotification extends Document {
type: NotificationType;
severity: ISeverity;
message: any;
user: IUser;
}

View File

@@ -0,0 +1,11 @@
import { Model, model } from 'mongoose';
import { IUser } from '../user/user.interface';
import { INotification, ISeverity, NotificationType } from './notification.interface';
import { schemaNotification } from './notification.schema';
const modelNotification: Model<INotification> = model<INotification>('Notification', schemaNotification, 'Notification');
export { modelNotification as Notification };
export function addNotification(type: NotificationType, severity: ISeverity, message: any, user: IUser) {
return modelNotification.create({ type, severity, message, user });
}

View File

@@ -0,0 +1,15 @@
import { Schema, SchemaTypes } from 'mongoose';
const schemaNotification = new Schema({
type: { type: String, required: true },
severity: { type: Number, required: true },
message: { type: Object, required: true },
user: { type: SchemaTypes.ObjectId }
}, {
timestamps: {
createdAt: true,
updatedAt: false
},
versionKey: false
});
export { schemaNotification };

View File

@@ -188,10 +188,9 @@
}
},
"@types/node": {
"version": "14.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.10.tgz",
"integrity": "sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA==",
"dev": true
"version": "14.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz",
"integrity": "sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw=="
},
"@types/qs": {
"version": "6.9.5",

View File

@@ -17,6 +17,7 @@
"author": "Mondei1",
"license": "GPL-3.0-or-later",
"dependencies": {
"@types/node": "^14.14.9",
"amqplib": "^0.6.0",
"argon2": "^0.27.0",
"body-parser": "^1.19.0",

View File

@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "ES5",
"target": "ES2020",
"lib": ["ES2020"],
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",