chore: archive finance, mail, moodlit apps and rename voxel-lava

- Move finance, mail, moodlit to apps-archived for later development
- Rename games/voxel-lava to games/voxelava

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-05 13:13:15 +01:00
parent c3c272abc9
commit ace7fa8f7f
427 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,25 @@
import { apiClient } from './client';
import type { Account, CreateAccountInput, UpdateAccountInput } from '@finance/shared';
export const accountsApi = {
getAll: () => apiClient.get<Account[]>('/accounts'),
getAllIncludingArchived: () => apiClient.get<Account[]>('/accounts/all'),
getOne: (id: string) => apiClient.get<Account>(`/accounts/${id}`),
getTotals: () =>
apiClient.get<{ currency: string; total: number; count: number }[]>('/accounts/totals'),
create: (data: CreateAccountInput) => apiClient.post<Account>('/accounts', data),
update: (id: string, data: UpdateAccountInput) => apiClient.put<Account>(`/accounts/${id}`, data),
delete: (id: string) => apiClient.delete<{ success: boolean }>(`/accounts/${id}`),
archive: (id: string) => apiClient.post<Account>(`/accounts/${id}/archive`),
unarchive: (id: string) => apiClient.post<Account>(`/accounts/${id}/unarchive`),
reorder: (accountIds: string[]) => apiClient.put<Account[]>('/accounts/reorder', { accountIds }),
};

View file

@ -0,0 +1,43 @@
import { apiClient } from './client';
import type { Budget, CreateBudgetInput, UpdateBudgetInput } from '@finance/shared';
// Budget with computed spending fields from API
export interface BudgetWithSpending {
id: string;
userId: string;
categoryId: string | null;
month: number;
year: number;
amount: string;
alertThreshold: string;
rolloverEnabled: boolean;
createdAt: Date;
updatedAt: Date;
spent: number;
remaining: number;
percentage: number;
category?: {
id: string;
name: string;
color: string;
icon?: string;
} | null;
}
export const budgetsApi = {
getAll: () => apiClient.get<Budget[]>('/budgets'),
getByMonth: (year: number, month: number) =>
apiClient.get<BudgetWithSpending[]>(`/budgets/month/${year}/${month}`),
getOne: (id: string) => apiClient.get<Budget>(`/budgets/${id}`),
create: (data: CreateBudgetInput) => apiClient.post<Budget>('/budgets', data),
update: (id: string, data: UpdateBudgetInput) => apiClient.put<Budget>(`/budgets/${id}`, data),
delete: (id: string) => apiClient.delete<{ success: boolean }>(`/budgets/${id}`),
copyFromPreviousMonth: (year: number, month: number) =>
apiClient.post<{ message: string; copied: number }>('/budgets/copy', { year, month }),
};

View file

@ -0,0 +1,30 @@
import { apiClient } from './client';
import type {
Category,
CreateCategoryInput,
UpdateCategoryInput,
CategoryType,
} from '@finance/shared';
export const categoriesApi = {
getAll: (type?: CategoryType) => {
const params = type ? `?type=${type}` : '';
return apiClient.get<Category[]>(`/categories${params}`);
},
getAllIncludingArchived: () => apiClient.get<Category[]>('/categories/all'),
getTree: () => apiClient.get<(Category & { children: Category[] })[]>('/categories/tree'),
getOne: (id: string) => apiClient.get<Category>(`/categories/${id}`),
create: (data: CreateCategoryInput) => apiClient.post<Category>('/categories', data),
update: (id: string, data: UpdateCategoryInput) =>
apiClient.put<Category>(`/categories/${id}`, data),
delete: (id: string) => apiClient.delete<{ success: boolean }>(`/categories/${id}`),
seed: () =>
apiClient.post<{ message: string; seeded: boolean; count?: number }>('/categories/seed'),
};

View file

@ -0,0 +1,61 @@
import { PUBLIC_BACKEND_URL } from '$env/static/public';
class ApiClient {
private baseUrl: string;
private token: string | null = null;
constructor() {
this.baseUrl = PUBLIC_BACKEND_URL || 'http://localhost:3019';
}
setToken(token: string | null) {
this.token = token;
}
private async request<T>(
method: string,
path: string,
body?: unknown,
options?: RequestInit
): Promise<T> {
const url = `${this.baseUrl}/api/v1${path}`;
const headers: HeadersInit = {
'Content-Type': 'application/json',
...(this.token && { Authorization: `Bearer ${this.token}` }),
...options?.headers,
};
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
...options,
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Request failed' }));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
get<T>(path: string, options?: RequestInit): Promise<T> {
return this.request<T>('GET', path, undefined, options);
}
post<T>(path: string, body?: unknown, options?: RequestInit): Promise<T> {
return this.request<T>('POST', path, body, options);
}
put<T>(path: string, body?: unknown, options?: RequestInit): Promise<T> {
return this.request<T>('PUT', path, body, options);
}
delete<T>(path: string, options?: RequestInit): Promise<T> {
return this.request<T>('DELETE', path, undefined, options);
}
}
export const apiClient = new ApiClient();

View file

@ -0,0 +1,25 @@
import { apiClient } from './client';
interface ExchangeRate {
fromCurrency: string;
toCurrency: string;
rate: number;
date: string;
}
export const exchangeRatesApi = {
getAll: (baseCurrency = 'EUR') =>
apiClient.get<ExchangeRate[]>(`/exchange-rates?base=${baseCurrency}`),
getRate: (fromCurrency: string, toCurrency: string) =>
apiClient.get<number>(`/exchange-rates/rate?from=${fromCurrency}&to=${toCurrency}`),
convert: (amount: number, fromCurrency: string, toCurrency: string) =>
apiClient.get<number>(
`/exchange-rates/convert?amount=${amount}&from=${fromCurrency}&to=${toCurrency}`
),
seed: () => apiClient.post<{ message: string; seeded: boolean }>('/exchange-rates/seed'),
fetch: () => apiClient.post<void>('/exchange-rates/fetch'),
};

View file

@ -0,0 +1,9 @@
export { apiClient } from './client';
export { accountsApi } from './accounts';
export { categoriesApi } from './categories';
export { transactionsApi } from './transactions';
export { budgetsApi } from './budgets';
export { transfersApi } from './transfers';
export { reportsApi } from './reports';
export { settingsApi } from './settings';
export { exchangeRatesApi } from './exchange-rates';

View file

@ -0,0 +1,89 @@
import { apiClient } from './client';
import type { DashboardData, MonthlySummary, CategoryBreakdown, TrendData } from '@finance/shared';
interface Dashboard {
totals: { currency: string; amount: number }[];
currentMonth: {
year: number;
month: number;
income: number;
expense: number;
net: number;
};
budgets: {
id: string;
category: { id: string; name: string; color: string } | null;
amount: number;
spent: number;
percentage: number;
}[];
recentTransactions: unknown[];
}
interface CategoryBreakdownResponse {
startDate: string;
endDate: string;
type: string;
total: number;
categories: {
categoryId: string | null;
name: string;
color: string | null;
icon: string | null;
amount: number;
count: number;
percentage: number;
}[];
}
interface TrendsResponse {
months: number;
data: {
year: number;
month: number;
income: number;
expense: number;
net: number;
}[];
averages: {
income: number;
expense: number;
net: number;
};
}
interface CashFlowResponse {
startDate: string;
endDate: string;
startingBalance: number;
endingBalance: number;
data: {
date: string;
balance: number;
income: number;
expense: number;
}[];
}
export const reportsApi = {
getDashboard: () => apiClient.get<Dashboard>('/reports/dashboard'),
getMonthlySummary: (year?: number, month?: number) => {
const params = new URLSearchParams();
if (year) params.append('year', String(year));
if (month) params.append('month', String(month));
const query = params.toString();
return apiClient.get<MonthlySummary>(`/reports/monthly-summary${query ? `?${query}` : ''}`);
},
getCategoryBreakdown: (startDate: string, endDate: string, type?: 'income' | 'expense') => {
const params = new URLSearchParams({ startDate, endDate });
if (type) params.append('type', type);
return apiClient.get<CategoryBreakdownResponse>(`/reports/category-breakdown?${params}`);
},
getTrends: (months = 6) => apiClient.get<TrendsResponse>(`/reports/trends?months=${months}`),
getCashFlow: (startDate: string, endDate: string) =>
apiClient.get<CashFlowResponse>(`/reports/cash-flow?startDate=${startDate}&endDate=${endDate}`),
};

View file

@ -0,0 +1,8 @@
import { apiClient } from './client';
import type { UserSettings, UpdateUserSettingsInput } from '@finance/shared';
export const settingsApi = {
get: () => apiClient.get<UserSettings>('/settings'),
update: (data: UpdateUserSettingsInput) => apiClient.put<UserSettings>('/settings', data),
};

View file

@ -0,0 +1,49 @@
import { apiClient } from './client';
import type {
Transaction,
CreateTransactionInput,
UpdateTransactionInput,
TransactionFilters,
} from '@finance/shared';
interface PaginatedTransactions {
data: Transaction[];
total: number;
limit: number;
offset: number;
}
export const transactionsApi = {
getAll: (filters?: TransactionFilters) => {
const params = new URLSearchParams();
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
params.append(key, String(value));
}
});
}
const query = params.toString();
return apiClient.get<PaginatedTransactions>(`/transactions${query ? `?${query}` : ''}`);
},
getRecent: (limit = 10) => apiClient.get<Transaction[]>(`/transactions/recent?limit=${limit}`),
getSummary: (startDate: string, endDate: string) =>
apiClient.get<{
income: number;
expense: number;
net: number;
incomeCount: number;
expenseCount: number;
}>(`/transactions/summary?startDate=${startDate}&endDate=${endDate}`),
getOne: (id: string) => apiClient.get<Transaction>(`/transactions/${id}`),
create: (data: CreateTransactionInput) => apiClient.post<Transaction>('/transactions', data),
update: (id: string, data: UpdateTransactionInput) =>
apiClient.put<Transaction>(`/transactions/${id}`, data),
delete: (id: string) => apiClient.delete<{ success: boolean }>(`/transactions/${id}`),
};

View file

@ -0,0 +1,15 @@
import { apiClient } from './client';
import type { Transfer, CreateTransferInput, UpdateTransferInput } from '@finance/shared';
export const transfersApi = {
getAll: () => apiClient.get<Transfer[]>('/transfers'),
getOne: (id: string) => apiClient.get<Transfer>(`/transfers/${id}`),
create: (data: CreateTransferInput) => apiClient.post<Transfer>('/transfers', data),
update: (id: string, data: UpdateTransferInput) =>
apiClient.put<Transfer>(`/transfers/${id}`, data),
delete: (id: string) => apiClient.delete<{ success: boolean }>(`/transfers/${id}`),
};