131 lines
3.9 KiB
TypeScript

// routes/api/import/obligations.ts
// Import obligations from JSON; skip items that already exist (by id or by content).
import { define } from "../../../utils.ts";
import {
createObligation,
getObligationsByUser,
} from "../../../services/financhalServices.ts";
import { SessionCookie } from "../../auth/callback.ts";
type ImportItem = {
id?: string;
description?: string | null;
amount?: number | null;
frequency?: string | null;
due_date?: string | number | null;
};
function signature(ob: {
description?: string | null;
amount?: number | null;
frequency?: string | null;
due_date?: number | null;
}): string {
const d = ob.due_date ?? 0;
return `${ob.description ?? ""}|${ob.amount ?? 0}|${ob.frequency ?? ""}|${d}`;
}
export const handler = define.handlers({
async POST(ctx) {
try {
const stateData: SessionCookie = ctx.state.data;
const user_id = stateData?.user_id;
if (!user_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401, headers: { "Content-Type": "application/json" } },
);
}
const contentType = ctx.req.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
return new Response(
JSON.stringify({ error: "Content-Type must be application/json" }),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
const body = await ctx.req.json();
const raw = Array.isArray(body) ? body : body?.obligations;
if (!Array.isArray(raw)) {
return new Response(
JSON.stringify({
error: "Body must be an array or { obligations: [] }",
}),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
const existing = await getObligationsByUser(user_id);
const existingIds = new Set(existing.map((o) => o.id));
const existingSigs = new Set(
existing.map((o) =>
signature({
description: o.description,
amount: o.amount,
frequency: o.frequency,
due_date: o.due_date,
})
),
);
let added = 0;
for (const item of raw as ImportItem[]) {
const id = typeof item.id === "string" ? item.id : undefined;
if (id && existingIds.has(id)) continue;
const amount = item.amount != null ? Number(item.amount) : null;
const dueDateRaw = item.due_date;
let due_date: number;
if (dueDateRaw == null || dueDateRaw === "") {
due_date = 0;
} else if (typeof dueDateRaw === "number") {
due_date = dueDateRaw;
} else {
due_date =
Date.parse(String(dueDateRaw).replace(/Z?$/, "T00:00:00")) ||
0;
}
const desc = item.description != null ? String(item.description) : null;
const freq = (item.frequency != null ? String(item.frequency) : "") ||
"monthly";
const sig = signature({
description: desc,
amount,
frequency: freq,
due_date: due_date || undefined,
});
if (existingSigs.has(sig)) continue;
await createObligation(
user_id,
desc,
amount != null && amount > 0 ? amount : null,
freq,
due_date,
"",
);
added++;
existingSigs.add(sig);
}
return new Response(
JSON.stringify({
added,
total: raw.length,
skipped: raw.length - added,
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
} catch (e) {
console.error("Import obligations error:", e);
return new Response(
JSON.stringify({ error: "Failed to import obligations" }),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
},
});