add alert routes
This commit is contained in:
42
src/alert/alert.controller.ts
Normal file
42
src/alert/alert.controller.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { FastifyReply, FastifyRequest } from "fastify";
|
||||
import { PageQueryParams } from "../pagination";
|
||||
import { getUnreadCount, getUserAlerts, markAsRead } from "./alert.service";
|
||||
|
||||
export async function listAlertsHandler(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply
|
||||
) {
|
||||
const params = req.query as PageQueryParams;
|
||||
|
||||
try {
|
||||
const alerts = await getUserAlerts(req.user, params);
|
||||
return res.code(200).send(alerts);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function markReadHandler(req: FastifyRequest, res: FastifyReply) {
|
||||
const { alertId } = req.params as { alertId: string };
|
||||
|
||||
try {
|
||||
const updatedAlert = await markAsRead(alertId, req.user);
|
||||
if (updatedAlert == null)
|
||||
return res.code(404).send({ error: "resource not found" });
|
||||
return res.code(200).send(updatedAlert);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUnreadCountHandler(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const unreadCount = await getUnreadCount(req.user);
|
||||
return res.code(200).send({ unreadCount });
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
46
src/alert/alert.route.ts
Normal file
46
src/alert/alert.route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import {
|
||||
getUnreadCountHandler,
|
||||
listAlertsHandler,
|
||||
markReadHandler,
|
||||
} from "./alert.controller";
|
||||
import { $alert } from "./alert.schema";
|
||||
|
||||
export async function alertRoutes(fastify: FastifyInstance) {
|
||||
fastify.get(
|
||||
"",
|
||||
{
|
||||
schema: {
|
||||
response: { 200: $alert("listAlertResponse") },
|
||||
},
|
||||
config: { requiredClaims: ["alert:read"] },
|
||||
preHandler: [fastify.authorize],
|
||||
},
|
||||
listAlertsHandler
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/:alertId/read",
|
||||
{
|
||||
schema: {
|
||||
params: {
|
||||
type: "object",
|
||||
properties: { alertId: { type: "string" } },
|
||||
},
|
||||
response: { 200: $alert("alertResponse") },
|
||||
},
|
||||
config: { requiredClaims: ["alert:write"] },
|
||||
preHandler: [fastify.authorize],
|
||||
},
|
||||
markReadHandler
|
||||
);
|
||||
|
||||
fastify.get(
|
||||
"/count",
|
||||
{
|
||||
config: { requiredClaims: ["alert:read"] },
|
||||
preHandler: [fastify.authorize],
|
||||
},
|
||||
getUnreadCountHandler
|
||||
);
|
||||
}
|
||||
54
src/alert/alert.schema.ts
Normal file
54
src/alert/alert.schema.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { z } from "zod";
|
||||
import mongoose from "mongoose";
|
||||
import { buildJsonSchemas } from "fastify-zod";
|
||||
import { pageQueryParams } from "../pagination";
|
||||
|
||||
export const alertsModel = mongoose.model(
|
||||
"alert",
|
||||
new mongoose.Schema({
|
||||
tenantId: String,
|
||||
pid: { type: String, unique: true },
|
||||
title: { type: String, required: true },
|
||||
referenceId: String,
|
||||
referenceCollection: String,
|
||||
recipientType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["user", "team"],
|
||||
},
|
||||
recipientId: { type: mongoose.Types.ObjectId, required: true },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
readBy: {
|
||||
type: [String],
|
||||
default: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const alertResponse = z.object({
|
||||
pid: z.string(),
|
||||
title: z.string(),
|
||||
read: z.boolean(),
|
||||
referenceId: z.string().optional(),
|
||||
referenceCollection: z.string().optional(),
|
||||
recipientType: z.enum(["user", "team"]),
|
||||
createdAt: z.date(),
|
||||
});
|
||||
|
||||
const listAlertResponse = z.object({
|
||||
alerts: z.array(alertResponse),
|
||||
metadata: z.object({
|
||||
count: z.number(),
|
||||
page: z.number(),
|
||||
pageSize: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { schemas: alertSchemas, $ref: $alert } = buildJsonSchemas(
|
||||
{
|
||||
alertResponse,
|
||||
listAlertResponse,
|
||||
pageQueryParams,
|
||||
},
|
||||
{ $id: "alert" }
|
||||
);
|
||||
110
src/alert/alert.service.ts
Normal file
110
src/alert/alert.service.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { AuthenticatedUser } from "../auth";
|
||||
import { PageQueryParams } from "../pagination";
|
||||
import { dbEvents } from "../realtime";
|
||||
import { generateId } from "../utils/id";
|
||||
import { alertsModel } from "./alert.schema";
|
||||
|
||||
export async function createAlert(
|
||||
tenantId: string,
|
||||
title: string,
|
||||
recipientType: "user" | "team",
|
||||
recipientId: string,
|
||||
referenceId?: string,
|
||||
referenceCollection?: string
|
||||
) {
|
||||
const newAlert = await alertsModel.create({
|
||||
tenantId,
|
||||
pid: generateId(),
|
||||
title,
|
||||
recipientType,
|
||||
recipientId,
|
||||
referenceId,
|
||||
referenceCollection,
|
||||
});
|
||||
|
||||
dbEvents.emit(
|
||||
"alert",
|
||||
{
|
||||
type: "insert",
|
||||
collection: "alerts",
|
||||
document: {
|
||||
pid: newAlert.pid,
|
||||
title,
|
||||
recipientType,
|
||||
recipientId,
|
||||
referenceId,
|
||||
referenceCollection,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
},
|
||||
["alert:read"]
|
||||
);
|
||||
}
|
||||
|
||||
export async function getUserAlerts(
|
||||
user: AuthenticatedUser,
|
||||
params: PageQueryParams
|
||||
) {
|
||||
const page = params.page || 1;
|
||||
const pageSize = params.pageSize || 10;
|
||||
|
||||
const filters: Array<object> = [
|
||||
{ recipientType: "user", recipientId: user.userId },
|
||||
];
|
||||
|
||||
if (user.role == "client")
|
||||
filters.push({ recipientType: "team", recipientId: user.orgId });
|
||||
else filters.push({ recipientType: "team" });
|
||||
|
||||
const alerts = await alertsModel
|
||||
.find({
|
||||
$or: filters,
|
||||
})
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(pageSize)
|
||||
.skip((page - 1) * pageSize);
|
||||
|
||||
const modifiedAlerts = alerts.map((alert) => {
|
||||
return {
|
||||
...alert.toObject(),
|
||||
read: alert.readBy.includes(user.userId) ? true : false,
|
||||
};
|
||||
});
|
||||
|
||||
const count = await alertsModel.countDocuments({ $or: filters });
|
||||
|
||||
return { alerts: modifiedAlerts, metadata: { count, page, pageSize } };
|
||||
}
|
||||
|
||||
export async function markAsRead(alertId: string, user: AuthenticatedUser) {
|
||||
const updatedAlert = await alertsModel.findOneAndUpdate(
|
||||
{ tenantId: user.tenantId, pid: alertId },
|
||||
{ $addToSet: { readBy: user.userId } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!updatedAlert) return null;
|
||||
|
||||
updatedAlert["read"] = updatedAlert.readBy.includes(user.userId)
|
||||
? true
|
||||
: false;
|
||||
|
||||
return updatedAlert;
|
||||
}
|
||||
|
||||
export async function getUnreadCount(user: AuthenticatedUser) {
|
||||
const filters: Array<object> = [
|
||||
{ recipientType: "user", recipientId: user.userId },
|
||||
];
|
||||
|
||||
if (user.role == "client")
|
||||
filters.push({ recipientType: "team", recipientId: user.orgId });
|
||||
else filters.push({ recipientType: "team" });
|
||||
|
||||
const alerts = await alertsModel.find({
|
||||
$or: filters,
|
||||
readBy: { $ne: user.userId },
|
||||
});
|
||||
|
||||
return alerts.length;
|
||||
}
|
||||
Reference in New Issue
Block a user