From 6dea12bb45467584825178e63a8d3e9f616741a4 Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Mon, 9 Jun 2025 16:45:05 +0530 Subject: [PATCH] add payments routes --- src/payments/payment.controller.ts | 34 +++++++++++ src/payments/payment.route.ts | 46 +++++++++++++++ src/payments/payment.schema.ts | 43 ++++++++++++++ src/payments/payments.service.ts | 94 ++++++++++++++++++++++++++++++ src/routes.ts | 2 + src/server.ts | 2 + src/utils/claims.ts | 3 +- src/utils/roles.ts | 2 + 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/payments/payment.controller.ts create mode 100644 src/payments/payment.route.ts create mode 100644 src/payments/payment.schema.ts create mode 100644 src/payments/payments.service.ts diff --git a/src/payments/payment.controller.ts b/src/payments/payment.controller.ts new file mode 100644 index 0000000..eacabcf --- /dev/null +++ b/src/payments/payment.controller.ts @@ -0,0 +1,34 @@ +import { FastifyReply, FastifyRequest } from "fastify"; +import { getPayment, listPayments } from "./payments.service"; +import { PageQueryParams } from "../pagination"; + +export async function getPaymentHandler( + req: FastifyRequest, + res: FastifyReply +) { + const { paymentId } = req.params as { paymentId: string }; + + try { + const payment = await getPayment(paymentId, req.user); + if (payment === null) + return res.code(404).send({ error: "resource not found" }); + + return res.code(200).send(payment); + } catch (err) { + return err; + } +} + +export async function listPaymentHandler( + req: FastifyRequest, + res: FastifyReply +) { + const queryParams = req.query as PageQueryParams; + + try { + const paymentList = await listPayments(queryParams, req.user); + return res.code(200).send(paymentList); + } catch (err) { + return err; + } +} diff --git a/src/payments/payment.route.ts b/src/payments/payment.route.ts new file mode 100644 index 0000000..54f46cc --- /dev/null +++ b/src/payments/payment.route.ts @@ -0,0 +1,46 @@ +import { FastifyInstance } from "fastify"; +import { getPaymentHandler, listPaymentHandler } from "./payment.controller"; +import { $payment } from "./payment.schema"; + +export async function paymentRoutes(fastify: FastifyInstance) { + fastify.get( + "/:paymentId", + { + schema: { + params: { + type: "object", + properties: { + paymentId: { type: "string" }, + }, + }, + }, + config: { requiredClaims: ["payment:read"] }, + preHandler: [fastify.authorize], + }, + getPaymentHandler + ); + + fastify.get( + "/", + { + schema: { + params: $payment("pageQueryParams"), + }, + config: { requiredClaims: ["payment:read"] }, + preHandler: [fastify.authorize], + }, + listPaymentHandler + ); + + fastify.get( + "/search", + { + schema: { + params: $payment("pageQueryParams"), + }, + config: { requiredClaims: ["payment:read"] }, + preHandler: [fastify.authorize], + }, + listPaymentHandler + ); +} diff --git a/src/payments/payment.schema.ts b/src/payments/payment.schema.ts new file mode 100644 index 0000000..0d1e524 --- /dev/null +++ b/src/payments/payment.schema.ts @@ -0,0 +1,43 @@ +import { buildJsonSchemas } from "fastify-zod"; +import mongoose from "mongoose"; +import { pageQueryParams } from "../pagination"; + +const paymentSchema = new mongoose.Schema({ + permitNumber: String, + recordId: String, + tenantId: { + type: String, + required: true, + }, + pid: { + type: String, + unique: true, + }, + permitPid: String, + county: Object, + client: { + type: mongoose.Types.ObjectId, + ref: "organization", + }, + clientData: Object, + address: Object, + permitLink: String, + cardEnding: String, + totalPaid: Number, + receiptNo: String, + fileId: String, + paymentDate: Date, +}); + +export const paymentModel = mongoose.model("payment", paymentSchema); + +export const paymentFields = Object.keys(paymentSchema.paths).filter( + (path) => path !== "__v" +); + +export const { schemas: paymentSchemas, $ref: $payment } = buildJsonSchemas( + { + pageQueryParams, + }, + { $id: "payment" } +); diff --git a/src/payments/payments.service.ts b/src/payments/payments.service.ts new file mode 100644 index 0000000..e624d94 --- /dev/null +++ b/src/payments/payments.service.ts @@ -0,0 +1,94 @@ +import mongoose from "mongoose"; +import { AuthenticatedUser } from "../auth"; +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { paymentFields, paymentModel } from "./payment.schema"; + +export async function getPayment(paymentId: string, user: AuthenticatedUser) { + const paymentObj = await paymentModel.findOne({ + tenantId: user.tenantId, + pid: paymentId, + }); + + if (user.role === "client" && paymentObj.client.toString() !== user.orgId) + return null; + + return paymentObj; +} + +export async function listPayments( + params: PageQueryParams, + user: AuthenticatedUser +) { + const page = params.page || 1; + const pageSize = params.pageSize || 10; + const sortObj = getSortObject(params, paymentFields); + const filterObj = getFilterObject(params) || []; + + if (user.role == "client") { + filterObj.push({ client: new mongoose.Types.ObjectId(user.orgId) }); + } + + const pipeline: Array = [ + { $match: { $and: [{ tenantId: user.tenantId }, ...filterObj] } }, + ]; + + if (params.searchToken) { + const regex = new RegExp(params.searchToken, "i"); + + pipeline.push({ + $match: { + $or: [ + { permitNumber: { $regex: regex } }, + { "address.full_address": { $regex: regex } }, + ], + }, + }); + } + + pipeline.push( + { + $project: { + _id: 1, + permitNumber: 1, + recordId: 1, + tenantId: 1, + pid: 1, + permitPid: 1, + county: 1, + client: 1, + clientData: 1, + address: 1, + permitLink: 1, + cardEnding: 1, + totalPaid: 1, + receiptNo: 1, + fileId: 1, + paymentDate: 1, + }, + }, + { + $facet: { + metadata: [{ $count: "count" }], + data: [ + { $sort: sortObj }, + { $skip: (page - 1) * pageSize }, + { $limit: pageSize }, + ], + }, + } + ); + + const paymentsList = await paymentModel.aggregate(pipeline); + + if (paymentsList[0].data.length === 0) + return { permits: [], metadata: { count: 0, page, pageSize } }; + + return { + payments: paymentsList[0]?.data, + metadata: { + count: paymentsList[0].metadata[0].count, + page, + pageSize, + }, + }; +} diff --git a/src/routes.ts b/src/routes.ts index 35ce9c5..079e10e 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -14,6 +14,7 @@ import { mailProxyRoutes } from "./mailProxy/mailProxy.route"; import { viewRoutes } from "./view/view.route"; import { processedRoutes } from "./processed/processed.route"; import { ctaskRoutes } from "./ctask/ctask.route"; +import { paymentRoutes } from "./payments/payment.route"; export default async function routes(fastify: FastifyInstance) { fastify.addHook("preHandler", authHandler); @@ -30,5 +31,6 @@ export default async function routes(fastify: FastifyInstance) { fastify.register(configRoutes, { prefix: "/config" }); fastify.register(viewRoutes, { prefix: "/views" }); fastify.register(processedRoutes, { prefix: "/processed" }); + fastify.register(paymentRoutes, { prefix: "/payments" }); fastify.register(realTimeRoutes); } diff --git a/src/server.ts b/src/server.ts index 9a748ce..326879b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -22,6 +22,7 @@ import { mailSchemas } from "./mailProxy/mailProxy.schema"; import { viewSchemas } from "./view/view.schema"; import { processedSchemas } from "./processed/processed.schema"; import { cTaskSchemas } from "./ctask/ctask.schema"; +import { paymentSchemas } from "./payments/payment.schema"; const app = fastify({ logger: true, trustProxy: true }); @@ -57,6 +58,7 @@ for (const schema of [ ...mailSchemas, ...viewSchemas, ...processedSchemas, + ...paymentSchemas, ]) { app.addSchema(schema); } diff --git a/src/utils/claims.ts b/src/utils/claims.ts index 5f6e262..9e417d9 100644 --- a/src/utils/claims.ts +++ b/src/utils/claims.ts @@ -34,4 +34,5 @@ export type Claim = | "view:delete" | "note:read" | "note:write" - | "note:delete"; + | "note:delete" + | "payment:read"; diff --git a/src/utils/roles.ts b/src/utils/roles.ts index 3dfa663..7651739 100644 --- a/src/utils/roles.ts +++ b/src/utils/roles.ts @@ -36,6 +36,7 @@ export const rules: Record< "note:read", "note:write", "note:delete", + "payment:read", ], hiddenFields: { orgs: ["__v"], @@ -73,6 +74,7 @@ export const rules: Record< "note:read", "note:write", "note:delete", + "payment:read", ], hiddenFields: { orgs: ["__v"],