removed unneccesary files
All checks were successful
Deploy / deploy (push) Successful in 1m29s
Security Tests / security-non-db (push) Successful in 20s
Security Tests / security-db (push) Successful in 24s

This commit is contained in:
2026-03-21 17:30:11 -05:00
parent 952684fc25
commit 9c7f4d5139
93 changed files with 107 additions and 7734 deletions

View File

@@ -10,6 +10,11 @@ import { PrismaClient } from "@prisma/client";
import { z } from "zod";
import { getUserMidnightFromDateOnly } from "./allocator.js";
import { fromZonedTime, toZonedTime } from "date-fns-tz";
import {
computeDepositShares,
computeOverdraftShares,
computeWithdrawShares,
} from "./services/category-shares.js";
import healthRoutes from "./routes/health.js";
import sessionRoutes from "./routes/session.js";
import userRoutes from "./routes/user.js";
@@ -429,199 +434,26 @@ function calculateNextDueDate(currentDueDate: Date, frequency: string, timezone:
switch (frequency) {
case "weekly":
zoned.setUTCDate(zoned.getUTCDate() + 7);
zoned.setDate(zoned.getDate() + 7);
break;
case "biweekly":
zoned.setUTCDate(zoned.getUTCDate() + 14);
zoned.setDate(zoned.getDate() + 14);
break;
case "monthly": {
const targetDay = zoned.getUTCDate();
const nextMonth = zoned.getUTCMonth() + 1;
const nextYear = zoned.getUTCFullYear() + Math.floor(nextMonth / 12);
const nextMonthIndex = nextMonth % 12;
const lastDay = new Date(Date.UTC(nextYear, nextMonthIndex + 1, 0)).getUTCDate();
zoned.setUTCFullYear(nextYear, nextMonthIndex, Math.min(targetDay, lastDay));
const targetDay = zoned.getDate();
zoned.setDate(1);
zoned.setMonth(zoned.getMonth() + 1);
const lastDay = new Date(zoned.getFullYear(), zoned.getMonth() + 1, 0).getDate();
zoned.setDate(Math.min(targetDay, lastDay));
break;
}
default:
return base;
}
zoned.setUTCHours(0, 0, 0, 0);
zoned.setHours(0, 0, 0, 0);
return fromZonedTime(zoned, timezone);
}
function jsonBigIntSafe(obj: unknown) {
return JSON.parse(
JSON.stringify(obj, (_k, v) => (typeof v === "bigint" ? Number(v) : v))
);
}
type PercentCategory = {
id: string;
percent: number;
balanceCents: bigint | null;
};
function computePercentShares(categories: PercentCategory[], amountCents: number) {
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
const shares = categories.map((cat) => {
const raw = (amountCents * cat.percent) / percentTotal;
const floored = Math.floor(raw);
return {
id: cat.id,
balanceCents: Number(cat.balanceCents ?? 0n),
share: floored,
frac: raw - floored,
};
});
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
shares
.slice()
.sort((a, b) => b.frac - a.frac)
.forEach((s) => {
if (remainder > 0) {
s.share += 1;
remainder -= 1;
}
});
if (shares.some((s) => s.share > s.balanceCents)) {
return { ok: false as const, reason: "insufficient_balances" };
}
return { ok: true as const, shares };
}
function computeWithdrawShares(categories: PercentCategory[], amountCents: number) {
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
const working = categories.map((cat) => ({
id: cat.id,
percent: cat.percent,
balanceCents: Number(cat.balanceCents ?? 0n),
share: 0,
}));
let remaining = Math.max(0, Math.floor(amountCents));
let safety = 0;
while (remaining > 0 && safety < 1000) {
safety += 1;
const eligible = working.filter((c) => c.balanceCents > 0 && c.percent > 0);
if (eligible.length === 0) break;
const totalPercent = eligible.reduce((sum, cat) => sum + cat.percent, 0);
if (totalPercent <= 0) break;
const provisional = eligible.map((cat) => {
const raw = (remaining * cat.percent) / totalPercent;
const floored = Math.floor(raw);
return {
id: cat.id,
raw,
floored,
remainder: raw - floored,
};
});
let sumBase = provisional.reduce((sum, p) => sum + p.floored, 0);
let leftovers = remaining - sumBase;
provisional
.slice()
.sort((a, b) => b.remainder - a.remainder)
.forEach((p) => {
if (leftovers > 0) {
p.floored += 1;
leftovers -= 1;
}
});
let allocatedThisRound = 0;
for (const p of provisional) {
const entry = working.find((w) => w.id === p.id);
if (!entry) continue;
const take = Math.min(p.floored, entry.balanceCents);
if (take > 0) {
entry.balanceCents -= take;
entry.share += take;
allocatedThisRound += take;
}
}
remaining -= allocatedThisRound;
if (allocatedThisRound === 0) break;
}
if (remaining > 0) {
return { ok: false as const, reason: "insufficient_balances" };
}
return {
ok: true as const,
shares: working.map((c) => ({ id: c.id, share: c.share })),
};
}
function computeOverdraftShares(categories: PercentCategory[], amountCents: number) {
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
const shares = categories.map((cat) => {
const raw = (amountCents * cat.percent) / percentTotal;
const floored = Math.floor(raw);
return {
id: cat.id,
share: floored,
frac: raw - floored,
};
});
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
shares
.slice()
.sort((a, b) => b.frac - a.frac)
.forEach((s) => {
if (remainder > 0) {
s.share += 1;
remainder -= 1;
}
});
return { ok: true as const, shares };
}
function computeDepositShares(categories: PercentCategory[], amountCents: number) {
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
const shares = categories.map((cat) => {
const raw = (amountCents * cat.percent) / percentTotal;
const floored = Math.floor(raw);
return {
id: cat.id,
share: floored,
frac: raw - floored,
};
});
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
shares
.slice()
.sort((a, b) => b.frac - a.frac)
.forEach((s) => {
if (remainder > 0) {
s.share += 1;
remainder -= 1;
}
});
return { ok: true as const, shares };
}
const DEFAULT_VARIABLE_CATEGORIES = [
{ name: "Essentials", percent: 50, priority: 10, isSavings: false },
{ name: "Savings", percent: 30, priority: 20, isSavings: true },