From b826a133a465c5c790c681ca4fd94e07cf6c333b Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Mon, 20 Apr 2026 10:49:06 +0330 Subject: [PATCH] YARA-814 --- src/app.module.ts | 5 + src/helpers/mongoose-fa-timestamps.plugin.ts | 98 ++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/helpers/mongoose-fa-timestamps.plugin.ts diff --git a/src/app.module.ts b/src/app.module.ts index 293300b..ebd86b2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,6 +19,7 @@ import { SandHubModule } from "./sand-hub/sand-hub.module"; import { ReportsModule } from "./reports/reports.module"; import { RequestManagementModule } from "./request-management/request-management.module"; import { UsersModule } from "./users/users.module"; +import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plugin"; import { CronModule } from "./utils/cron/cron.module"; import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module"; @@ -44,6 +45,10 @@ dotenv.config({ path: `.${process.env.NODE_ENV}.env` }); authMechanism: "SCRAM-SHA-256", tls: true, tlsAllowInvalidCertificates: true, + connectionFactory: (connection) => { + applyIranFaTimestampPlugin(connection); + return connection; + }, }, ), UsersModule, diff --git a/src/helpers/mongoose-fa-timestamps.plugin.ts b/src/helpers/mongoose-fa-timestamps.plugin.ts new file mode 100644 index 0000000..47f56c5 --- /dev/null +++ b/src/helpers/mongoose-fa-timestamps.plugin.ts @@ -0,0 +1,98 @@ +import { Connection, Query, Schema } from "mongoose"; + +const PERSIAN_TO_ENGLISH: Record = { + "۰": "0", + "۱": "1", + "۲": "2", + "۳": "3", + "۴": "4", + "۵": "5", + "۶": "6", + "۷": "7", + "۸": "8", + "۹": "9", +}; + +function normalizeDigits(value: string): string { + return value.replace(/[۰-۹]/g, (ch) => PERSIAN_TO_ENGLISH[ch] ?? ch); +} + +function toIranFaDateTimeTuple(input: Date): [string, string] { + const dateFa = normalizeDigits( + input.toLocaleDateString("fa-IR", { + timeZone: "Asia/Tehran", + year: "numeric", + month: "2-digit", + day: "2-digit", + }), + ); + const timeFa = normalizeDigits( + input.toLocaleTimeString("fa-IR", { + timeZone: "Asia/Tehran", + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }), + ); + return [dateFa, timeFa]; +} + +function writeFaTimestampsOnUpdate(query: Query): void { + const nowFa = toIranFaDateTimeTuple(new Date()); + const update = (query.getUpdate() ?? {}) as Record; + + if (!update.$set || typeof update.$set !== "object") { + update.$set = {}; + } + + if (update.$set.createdAtFa == null && update.createdAtFa == null) { + update.$set.createdAtFa = nowFa; + } + update.$set.updatedAtFa = nowFa; + query.setUpdate(update); +} + +/** + * Adds `createdAtFa` / `updatedAtFa` (Iran timezone, UTC+03:30 offset string) + * to all schemas that use mongoose timestamps. + */ +export function applyIranFaTimestampPlugin(connection: Connection): void { + connection.plugin((schema: Schema) => { + const timestamps = schema.get("timestamps"); + if (!timestamps) return; + + schema.add({ + createdAtFa: { type: [String], index: true }, + updatedAtFa: { type: [String], index: true }, + }); + + schema.pre("save", function (next) { + const nowFa = toIranFaDateTimeTuple(new Date()); + if (!(this as any).createdAtFa) { + (this as any).createdAtFa = nowFa; + } + (this as any).updatedAtFa = nowFa; + next(); + }); + + schema.pre("insertMany", function (next, docs: any[]) { + const nowFa = toIranFaDateTimeTuple(new Date()); + for (const doc of docs) { + if (!doc.createdAtFa) doc.createdAtFa = nowFa; + doc.updatedAtFa = nowFa; + } + next(); + }); + + schema.pre("findOneAndUpdate", function () { + writeFaTimestampsOnUpdate(this); + }); + schema.pre("updateOne", function () { + writeFaTimestampsOnUpdate(this); + }); + schema.pre("updateMany", function () { + writeFaTimestampsOnUpdate(this); + }); + }); +}