add alert routes

This commit is contained in:
2025-06-21 12:29:53 +05:30
parent c6bb01d8e4
commit e821c8d11d
12 changed files with 336 additions and 6 deletions

View 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
View 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
View 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
View 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;
}