Files
budget/src/lib/dataService.ts
T
nmemmertandClaude Sonnet 4.6 20ea25f4de Major feature release: security hardening + Goals, Rules, search, charts
Security:
- Replace SHA-256 password hashing with bcrypt (cost 12) + timing-safe compare
- Introduce server-side session tokens (x-session-token header); drop x-user-id
- Add /api/auth/logout endpoint to invalidate sessions
- Enforce 8-char minimum password server-side
- Fix password reset: now uses real signed tokens with 1-hour TTL

Features:
- Goals: savings goals with progress bars, target dates, add/withdraw funds
- Auto-categorization Rules: keyword → envelope mapping applied on transaction add
- Transaction search/filter: description, account, envelope, date range
- Transaction table: shows envelope color dot, running balance column
- Monthly Spending History chart (last 6 months income vs expenses bar chart)
- Spending Insights: month-over-month comparison + overspent envelope callouts
- Budget progress expanded: per-envelope bars with rollover indicator
- Envelope rollover toggle: unspent funds can carry forward
- Recurring transactions auto-applied on login
- Save indicator in nav (Saving… / Saved ✓ / Save failed)
- Nav overflow fixed: all tabs scroll horizontally instead of clipping
- PWA manifest added for home-screen install support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 08:22:24 -04:00

90 lines
2.3 KiB
TypeScript

import { AuthService } from './authService';
interface Envelope {
id: string;
name: string;
allocated: number;
spent: number;
color: string;
accountId: string;
incomeAllocation?: number;
incomeAllocationType?: 'percentage' | 'fixed';
}
interface Transaction {
id: string;
envelopeId?: string;
accountId: string;
amount: number;
description: string;
date: Date | string;
category?: string;
}
interface Account {
id: string;
name: string;
type: 'checking' | 'savings' | 'credit_card' | 'mortgage' | 'investment' | 'loan';
balance: number;
startingBalance?: number;
institution?: string;
accountNumber?: string;
color: string;
isActive: boolean;
defaultPaycheckAmount?: number;
}
interface UserData {
accounts: Account[];
envelopes: Envelope[];
transactions: Transaction[];
setupCompleted: boolean;
goals?: any[];
transactionRules?: any[];
}
function getAuthHeaders(): HeadersInit {
const token = AuthService.getSessionToken();
if (!token) throw new Error('Not authenticated');
return { 'Content-Type': 'application/json', 'x-session-token': token };
}
export class DataService {
// kept for legacy callers in page.tsx — no-op now that session handles identity
static setUserId(_userId: string | null) {}
static getUserId(): string | null { return AuthService.getCurrentUser()?.userId ?? null; }
static clearUserId() {}
static async saveUserData(data: UserData): Promise<void> {
const response = await fetch('/api/data', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to save data');
}
}
static async loadUserData(): Promise<UserData | null> {
const response = await fetch('/api/data', {
method: 'GET',
headers: getAuthHeaders(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to load data');
}
const result = await response.json();
return result.data;
}
static async updateUserData(updates: Partial<UserData>): Promise<void> {
const currentData = await this.loadUserData();
if (currentData) {
await this.saveUserData({ ...currentData, ...updates });
}
}
}