131 lines
3.9 KiB
TypeScript
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" } },
|
|
);
|
|
}
|
|
},
|
|
});
|