Notification system enhanced
- Base for notifcation center - Show text in map if no data is available
This commit is contained in:
@@ -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));
|
||||
|
||||
|
||||
22
backend/endpoints/notification.ts
Normal file
22
backend/endpoints/notification.ts
Normal 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" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
18
backend/models/notifications/notification.interface.ts
Normal file
18
backend/models/notifications/notification.interface.ts
Normal 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;
|
||||
}
|
||||
11
backend/models/notifications/notification.model.ts
Normal file
11
backend/models/notifications/notification.model.ts
Normal 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 });
|
||||
}
|
||||
15
backend/models/notifications/notification.schema.ts
Normal file
15
backend/models/notifications/notification.schema.ts
Normal 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 };
|
||||
7
backend/package-lock.json
generated
7
backend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020"],
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./",
|
||||
|
||||
Reference in New Issue
Block a user