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 isMissingFaTuple(value: unknown): boolean { if (value == null) return true; if (!Array.isArray(value)) return false; if (value.length < 2) return true; const [d, t] = value; return !d || !t; } 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 = {}; } update.$set.updatedAtFa = nowFa; // Keep createdAtFa immutable on regular updates; set only for upsert inserts. const opts = (query as any).getOptions?.() ?? {}; if (opts.upsert) { if (!update.$setOnInsert || typeof update.$setOnInsert !== "object") { update.$setOnInsert = {}; } if (isMissingFaTuple(update.$setOnInsert.createdAtFa)) { update.$setOnInsert.createdAtFa = nowFa; } } query.setUpdate(update); } /** * Adds `createdAtFa` / `updatedAtFa` (Iran timezone tuple: [date, time]) * 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 (isMissingFaTuple((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 (isMissingFaTuple(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); }); }); }