diff --git a/src/notification/notification.controller.ts b/src/notification/notification.controller.ts new file mode 100644 index 0000000..5852188 --- /dev/null +++ b/src/notification/notification.controller.ts @@ -0,0 +1,44 @@ +import { FastifyRequest, FastifyReply } from "fastify"; +import { PageQueryParams } from "../pagination"; +import { listNotifications, updateNotification } from "./notification.service"; +import { UpdateNotificationInput } from "./notification.schema"; + +export async function listNotificationsHandler( + req: FastifyRequest, + res: FastifyReply +) { + const queryParams = req.query as PageQueryParams; + + try { + const notificationList = await listNotifications( + queryParams, + req.user.tenantId + ); + return res.code(200).send(notificationList); + } catch (err) { + return err; + } +} + +export async function updateNotificationHandler( + req: FastifyRequest, + res: FastifyReply +) { + const input = req.body as UpdateNotificationInput; + const { notifId } = req.params as { notifId: string }; + + try { + const updatedNotification = await updateNotification( + notifId, + input, + req.user.tenantId + ); + + if (!updatedNotification) + return res.code(404).send({ error: "resource not found" }); + + return res.code(200).send(updatedNotification); + } catch (err) { + return err; + } +} diff --git a/src/notification/notification.route.ts b/src/notification/notification.route.ts new file mode 100644 index 0000000..9c2aa32 --- /dev/null +++ b/src/notification/notification.route.ts @@ -0,0 +1,33 @@ +import { FastifyInstance } from "fastify"; +import { $notification } from "./notification.schema"; +import { + listNotificationsHandler, + updateNotificationHandler, +} from "./notification.controller"; + +export async function notificationRoutes(fastify: FastifyInstance) { + fastify.get( + "/", + { + schema: { + querystring: $notification("pageQueryParams"), + }, + config: { requiredClaims: ["notification:read"] }, + preHandler: [fastify.authorize], + }, + listNotificationsHandler + ); + + fastify.patch( + "/:notifId", + { + schema: { + params: { type: "object", properties: { notifId: { type: "string" } } }, + body: $notification("updateNotificationInput"), + }, + config: { requiredClaims: ["notification:write"] }, + preHandler: [fastify.authorize], + }, + updateNotificationHandler + ); +} diff --git a/src/notification/notification.schema.ts b/src/notification/notification.schema.ts new file mode 100644 index 0000000..6abe40a --- /dev/null +++ b/src/notification/notification.schema.ts @@ -0,0 +1,50 @@ +import { buildJsonSchemas } from "fastify-zod"; +import mongoose from "mongoose"; +import { z } from "zod"; +import { pageQueryParams } from "../pagination"; + +const notificationSchema = new mongoose.Schema({ + pid: { + type: String, + unique: true, + }, + tenantId: String, + permitNumber: String, + permitLink: String, + status: String, + changes: Object, + county: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + client: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + createdAt: Date, + updatedAt: Date, +}); + +export const notificationFields = Object.keys(notificationSchema.paths).filter( + (path) => path !== "__v" +); + +export const notificationModel = mongoose.model( + "notification", + notificationSchema +); + +const updateNotificationInput = z.object({ + status: z.string(), +}); + +export type UpdateNotificationInput = z.infer; + +export const { schemas: notificationSchemas, $ref: $notification } = + buildJsonSchemas( + { + updateNotificationInput, + pageQueryParams, + }, + { $id: "notification" } + ); diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts new file mode 100644 index 0000000..a5e85a2 --- /dev/null +++ b/src/notification/notification.service.ts @@ -0,0 +1,57 @@ +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { + notificationFields, + notificationModel, + UpdateNotificationInput, +} from "./notification.schema"; + +export async function updateNotification( + notifId: string, + input: UpdateNotificationInput, + tenantId: string +) { + return await notificationModel.findOneAndUpdate( + { $and: [{ tenantId: tenantId }, { pid: notifId }] }, + { + ...input, + updatedAt: new Date(), + }, + { new: true } + ); +} + +export async function listNotifications( + params: PageQueryParams, + tenantId: string +) { + const page = params.page || 1; + const pageSize = params.pageSize || 10; + const sortObj = getSortObject(params, notificationFields); + const filterObj = getFilterObject(params, notificationFields); + + const notifications = await notificationModel.aggregate([ + { $match: { $and: [{ tenantId: tenantId }, ...filterObj] } }, + { + $facet: { + metadata: [{ $count: "count" }], + data: [ + { $skip: (page - 1) * pageSize }, + { $limit: pageSize }, + { $sort: sortObj }, + ], + }, + }, + ]); + + if (notifications[0].data.length === 0) + return { orgs: [], metadata: { count: 0, page, pageSize } }; + + return { + orgs: notifications[0].data, + metadata: { + count: notifications[0].metadata[0].count, + page, + pageSize, + }, + }; +} diff --git a/src/routes.ts b/src/routes.ts index ab1be94..3c6fdf7 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -8,6 +8,7 @@ import { fileRoutes } from "./file/file.route"; import { rtsRoutes } from "./rts/rts.route"; import { taskRoutes } from "./task/task.route"; import { realTimeRoutes } from "./realtime/realtime.route"; +import { notificationRoutes } from "./notification/notification.route"; export default async function routes(fastify: FastifyInstance) { fastify.addHook("preHandler", authHandler); @@ -18,5 +19,6 @@ export default async function routes(fastify: FastifyInstance) { fastify.register(fileRoutes, { prefix: "/files" }); fastify.register(rtsRoutes, { prefix: "/rts" }); fastify.register(taskRoutes, { prefix: "/tasks" }); + fastify.register(notificationRoutes, { prefix: "/notifications" }); fastify.register(realTimeRoutes); } diff --git a/src/server.ts b/src/server.ts index 91aab83..e1297ea 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,6 +14,7 @@ import { oauth } from "./oauth"; import { authRoutes } from "./auth/auth.route"; import { rtsSchemas } from "./rts/rts.schema"; import { taskSchemas } from "./task/task.schema"; +import { notificationSchemas } from "./notification/notification.schema"; const app = fastify({ logger: true }); @@ -41,6 +42,7 @@ for (const schema of [ ...fileSchemas, ...rtsSchemas, ...taskSchemas, + ...notificationSchemas, ]) { app.addSchema(schema); } diff --git a/src/utils/claims.ts b/src/utils/claims.ts index 2ff8ed7..ca240cc 100644 --- a/src/utils/claims.ts +++ b/src/utils/claims.ts @@ -19,4 +19,6 @@ export type Claim = | "rts:delete" | "task:read" | "task:write" - | "task:delete"; + | "task:delete" + | "notification:read" + | "notification:write"; diff --git a/src/utils/roles.ts b/src/utils/roles.ts index 5fb68a6..21c131f 100644 --- a/src/utils/roles.ts +++ b/src/utils/roles.ts @@ -24,6 +24,8 @@ export const rules: Record< "task:read", "task:write", "task:delete", + "notification:read", + "notification:write", ], hiddenFields: { orgs: ["__v"],