Ten routes accepted requests with no getCurrentUser check (transactions/[id], bulk, splits, tags-on-tx, splits/settle, statements/[id], tags, tags/[id], merchants, participants/[id]/balance), and by-id routes did no ownership check at all — any participant could read or modify another's data. Adds canAccessTransactions() (owner via statement/direct, or split participant), applies it to every transaction-scoped route, owner-scopes statements/[id], and rescopes splits/settle in raw SQL so settlement only touches splits the caller is party to. Also: all trip analytics now sum COALESCE(amount_aud, amount) instead of raw amount, matching every other analytics query — trip totals previously added foreign-currency amounts to AUD ones unit-less. And rules apply_split no longer delete+reinserts splits (which reset settled flags on every run) — it upserts share_percent and removes only participants no longer in the rule.
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { queryRaw } from "@/lib/db";
|
|
import { getCurrentUser } from "@/lib/auth";
|
|
|
|
// A split may be settled by the transaction's effective owner or by the
|
|
// participant the split belongs to.
|
|
const SCOPE = `
|
|
AND EXISTS (
|
|
SELECT 1 FROM transactions t
|
|
LEFT JOIN statements s ON s.id = t.statement_id
|
|
WHERE t.id = transaction_splits.transaction_id
|
|
AND (COALESCE(t.owner_id, s.owner_id) = $2 OR transaction_splits.participant_id = $2)
|
|
)`;
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const user = await getCurrentUser(req);
|
|
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
|
|
|
const body = await req.json();
|
|
const { participant_id, split_ids } = body as {
|
|
participant_id?: number;
|
|
split_ids?: number[];
|
|
};
|
|
|
|
if (participant_id) {
|
|
const rows = await queryRaw<{ id: number }>(
|
|
`UPDATE transaction_splits SET settled = true, settled_at = NOW()
|
|
WHERE participant_id = $1 AND settled = false ${SCOPE}
|
|
RETURNING id`,
|
|
[participant_id, user.id]
|
|
);
|
|
return NextResponse.json({ settled: rows.length });
|
|
}
|
|
|
|
if (split_ids?.length) {
|
|
const rows = await queryRaw<{ id: number }>(
|
|
`UPDATE transaction_splits SET settled = true, settled_at = NOW()
|
|
WHERE id = ANY($1::int[]) AND settled = false ${SCOPE}
|
|
RETURNING id`,
|
|
[split_ids, user.id]
|
|
);
|
|
return NextResponse.json({ settled: rows.length });
|
|
}
|
|
|
|
return NextResponse.json({ error: "participant_id or split_ids required" }, { status: 400 });
|
|
}
|