initial commit
This commit is contained in:
61
apps/Backend/src/utils/dateUtils.ts
Executable file
61
apps/Backend/src/utils/dateUtils.ts
Executable file
@@ -0,0 +1,61 @@
|
||||
|
||||
/**
|
||||
* Convert any OCR string-like value into a safe string.
|
||||
*/
|
||||
export function toStr(val: string | number | null | undefined): string {
|
||||
if (val == null) return "";
|
||||
return String(val).trim();
|
||||
}
|
||||
/**
|
||||
* Convert OCR date strings like "070825" (MMDDYY) into a JS Date object.
|
||||
* Example: "070825" → 2025-08-07.
|
||||
*/
|
||||
export function convertOCRDate(input: string | number | null | undefined): Date {
|
||||
const raw = toStr(input);
|
||||
|
||||
if (!/^\d{6}$/.test(raw)) {
|
||||
throw new Error(`Invalid OCR date format: ${raw}`);
|
||||
}
|
||||
|
||||
const month = parseInt(raw.slice(0, 2), 10) - 1;
|
||||
const day = parseInt(raw.slice(2, 4), 10);
|
||||
const year2 = parseInt(raw.slice(4, 6), 10);
|
||||
const year = year2 < 50 ? 2000 + year2 : 1900 + year2;
|
||||
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a DOB value to "YYYY-MM-DD" string expected by the Python agent.
|
||||
* - If dob is already "YYYY-MM-DD" string, returns it.
|
||||
* - If dob is an ISO datetime string or Date, returns YYYY-MM-DD derived from UTC parts (no timezone shifts).
|
||||
* - Returns null for invalid values.
|
||||
*/
|
||||
export function formatDobForAgent(dob: Date | string | null | undefined): string | null {
|
||||
if (!dob) return null;
|
||||
|
||||
// If it's a string in exact YYYY-MM-DD format, return as-is (most ideal).
|
||||
if (typeof dob === "string") {
|
||||
const simpleDateMatch = /^\d{4}-\d{2}-\d{2}$/.test(dob);
|
||||
if (simpleDateMatch) return dob;
|
||||
|
||||
// Otherwise try parsing as a Date/ISO string and use UTC parts
|
||||
const parsed = new Date(dob);
|
||||
if (isNaN(parsed.getTime())) return null;
|
||||
const y = parsed.getUTCFullYear();
|
||||
const m = String(parsed.getUTCMonth() + 1).padStart(2, "0");
|
||||
const d = String(parsed.getUTCDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
// If it's a Date object, use UTC getters to avoid timezone shifts
|
||||
if (dob instanceof Date) {
|
||||
if (isNaN(dob.getTime())) return null;
|
||||
const y = dob.getUTCFullYear();
|
||||
const m = String(dob.getUTCMonth() + 1).padStart(2, "0");
|
||||
const d = String(dob.getUTCDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
146
apps/Backend/src/utils/emptyTempFolder.ts
Executable file
146
apps/Backend/src/utils/emptyTempFolder.ts
Executable file
@@ -0,0 +1,146 @@
|
||||
// ../utils/emptyTempFolder.ts
|
||||
import fs from "fs/promises";
|
||||
import fsSync from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
/**
|
||||
* Remove EVERYTHING under the parent folder that contains filePath.
|
||||
* - Does NOT remove the parent folder itself (only its children).
|
||||
* - Uses fs.rm with recursive+force if available.
|
||||
* - Falls back to reliable manual recursion otherwise.
|
||||
* - Logs folder contents before and after.
|
||||
*
|
||||
* Throws on critical safety checks.
|
||||
*/
|
||||
export async function emptyFolderContainingFile(filePath?: string | null): Promise<void> {
|
||||
if (!filePath) return;
|
||||
|
||||
const absFile = path.resolve(String(filePath));
|
||||
const folder = path.dirname(absFile);
|
||||
|
||||
// Safety checks
|
||||
if (!folder) {
|
||||
throw new Error(`Refusing to clean: resolved folder empty for filePath=${filePath}`);
|
||||
}
|
||||
const parsed = path.parse(folder);
|
||||
if (folder === parsed.root) {
|
||||
throw new Error(`Refusing to clean root folder: ${folder}`);
|
||||
}
|
||||
const home = os.homedir();
|
||||
if (home && path.resolve(home) === path.resolve(folder)) {
|
||||
throw new Error(`Refusing to clean user's home directory: ${folder}`);
|
||||
}
|
||||
const base = path.basename(folder);
|
||||
if (!base || base.length < 2) {
|
||||
throw new Error(`Refusing to clean suspicious folder: ${folder}`);
|
||||
}
|
||||
|
||||
console.log(`[cleanup] emptyFolderContainingFile called for filePath=${filePath}`);
|
||||
console.log(`[cleanup] target folder=${folder}`);
|
||||
|
||||
// Read and log contents before
|
||||
let before: string[] = [];
|
||||
try {
|
||||
before = await fs.readdir(folder);
|
||||
console.log(`[cleanup] before (${before.length}):`, before);
|
||||
} catch (err) {
|
||||
console.error(`[cleanup] failed to read folder ${folder} before removal:`, err);
|
||||
// If we can't read, bail out (safety)
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Helper fallback: recursive remove
|
||||
async function recursiveRemove(p: string): Promise<void> {
|
||||
try {
|
||||
const st = await fs.lstat(p);
|
||||
if (st.isDirectory()) {
|
||||
const children = await fs.readdir(p);
|
||||
for (const c of children) {
|
||||
await recursiveRemove(path.join(p, c));
|
||||
}
|
||||
// remove directory after children removed
|
||||
try {
|
||||
await fs.rmdir(p);
|
||||
} catch (err) {
|
||||
// log and continue
|
||||
console.error(`[cleanup] rmdir failed for ${p}:`, err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await fs.unlink(p);
|
||||
} catch (err: any) {
|
||||
// On EPERM try chmod and retry once
|
||||
if (err.code === "EPERM" || err.code === "EACCES") {
|
||||
try {
|
||||
fsSync.chmodSync(p, 0o666);
|
||||
await fs.unlink(p);
|
||||
} catch (retryErr) {
|
||||
console.error(`[cleanup] unlink after chmod failed for ${p}:`, retryErr);
|
||||
throw retryErr;
|
||||
}
|
||||
} else if (err.code === "ENOENT") {
|
||||
// already gone — ignore
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.code === "ENOENT") return; // already gone
|
||||
// rethrow to allow caller to log
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything under folder (each top-level entry)
|
||||
for (const name of before) {
|
||||
const full = path.join(folder, name);
|
||||
try {
|
||||
if (typeof (fs as any).rm === "function") {
|
||||
// Node >= 14.14/16+: use fs.rm with recursive & force
|
||||
await (fs as any).rm(full, { recursive: true, force: true });
|
||||
console.log(`[cleanup] removed (fs.rm): ${full}`);
|
||||
} else {
|
||||
// fallback
|
||||
await recursiveRemove(full);
|
||||
console.log(`[cleanup] removed (recursive): ${full}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[cleanup] failed to remove ${full}:`, err);
|
||||
// Try chmod and retry once for stubborn files
|
||||
try {
|
||||
if (fsSync.existsSync(full)) {
|
||||
console.log(`[cleanup] attempting chmod+retry for ${full}`);
|
||||
try {
|
||||
fsSync.chmodSync(full, 0o666);
|
||||
if (typeof (fs as any).rm === "function") {
|
||||
await (fs as any).rm(full, { recursive: true, force: true });
|
||||
} else {
|
||||
await recursiveRemove(full);
|
||||
}
|
||||
console.log(`[cleanup] removed after chmod: ${full}`);
|
||||
continue;
|
||||
} catch (retryErr) {
|
||||
console.error(`[cleanup] retry after chmod failed for ${full}:`, retryErr);
|
||||
}
|
||||
} else {
|
||||
console.log(`[cleanup] ${full} disappeared before retry`);
|
||||
}
|
||||
} catch (permErr) {
|
||||
console.error(`[cleanup] chmod/retry error for ${full}:`, permErr);
|
||||
}
|
||||
// continue to next entry even if this failed
|
||||
}
|
||||
}
|
||||
|
||||
// Read and log contents after
|
||||
try {
|
||||
const after = await fs.readdir(folder);
|
||||
console.log(`[cleanup] after (${after.length}):`, after);
|
||||
} catch (err) {
|
||||
console.error(`[cleanup] failed to read folder ${folder} after removal:`, err);
|
||||
}
|
||||
|
||||
console.log(`[cleanup] finished cleaning folder: ${folder}`);
|
||||
}
|
||||
27
apps/Backend/src/utils/helpers.ts
Executable file
27
apps/Backend/src/utils/helpers.ts
Executable file
@@ -0,0 +1,27 @@
|
||||
export function normalizeInsuranceId(raw: unknown): string | undefined {
|
||||
if (raw === undefined || raw === null) return undefined;
|
||||
|
||||
// Accept numbers too (e.g. 12345), but prefer strings
|
||||
let s: string;
|
||||
if (typeof raw === "number") {
|
||||
s = String(raw);
|
||||
} else if (typeof raw === "string") {
|
||||
s = raw;
|
||||
} else {
|
||||
// Not acceptable type
|
||||
throw new Error("Insurance ID must be a numeric string.");
|
||||
}
|
||||
|
||||
// Remove all whitespace
|
||||
const cleaned = s.replace(/\s+/g, "");
|
||||
|
||||
// If empty after cleaning, treat as undefined
|
||||
if (cleaned === "") return undefined;
|
||||
|
||||
// Only digits allowed (since you said it's numeric)
|
||||
if (!/^\d+$/.test(cleaned)) {
|
||||
throw new Error("Insurance ID must contain only digits.");
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
12
apps/Backend/src/utils/prismaFileUtils.ts
Executable file
12
apps/Backend/src/utils/prismaFileUtils.ts
Executable file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Helper: convert Prisma CloudFile result to JSON-friendly object.
|
||||
*/
|
||||
export function serializeFile(f: any) {
|
||||
if (!f) return null;
|
||||
return {
|
||||
...f,
|
||||
fileSize: typeof f.fileSize === "bigint" ? f.fileSize.toString() : f.fileSize,
|
||||
createdAt: f.createdAt?.toISOString?.(),
|
||||
updatedAt: f.updatedAt?.toISOString?.(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user