Files
electric-cherry/src/lib/bank.ts
T
2026-10-13 11:50:26 -04:00

117 lines
3.7 KiB
TypeScript

export const bankPageSize = 25;
export const bankAttentionAgeMs = 5 * 60_000;
export interface BankQuery {
view: 'accounts' | 'transactions' | 'pending';
q: string;
balance: '' | 'funded' | 'limit' | 'pending';
sort: 'balance' | 'recent';
game: '' | 't5' | 't6';
server: string;
operation: '' | 'deposit' | 'withdraw';
from: string;
to: string;
offset: number;
player: string;
historyOffset: number;
}
export function readBankQuery(params: URLSearchParams): BankQuery {
function choice<T extends string>(key: string, values: readonly T[], fallback: T): T {
const value = params.get(key) || fallback;
if (!values.includes(value as T)) throw new Error(`Invalid ${key} filter.`);
return value as T;
}
function text(key: string, max: number) {
const value = (params.get(key) ?? '').trim();
if (value.length > max) throw new Error(`${key} is too long.`);
return value;
}
function offset(key: string) {
const value = Number(params.get(key) || 0);
if (!Number.isSafeInteger(value) || value < 0) throw new Error('Invalid page offset.');
return Math.floor(value / bankPageSize) * bankPageSize;
}
function date(key: string) {
const value = text(key, 10);
if (
value &&
(!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
!Number.isFinite(Date.parse(value)) ||
new Date(value).toISOString().slice(0, 10) !== value)
)
throw new Error('Use a valid date in YYYY-MM-DD format.');
return value;
}
const from = date('from');
const to = date('to');
if (from && to && from > to) throw new Error('The end date must follow the start date.');
return {
view: choice('view', ['accounts', 'transactions', 'pending'], 'accounts'),
q: text('q', 100),
balance: choice('balance', ['', 'funded', 'limit', 'pending'], ''),
sort: choice('sort', ['balance', 'recent'], 'balance'),
game: choice('game', ['', 't5', 't6'], ''),
server: text('server', 256),
operation: choice('operation', ['', 'deposit', 'withdraw'], ''),
from,
to,
offset: offset('offset'),
player: text('player', 128),
historyOffset: offset('historyOffset')
};
}
export interface BankIdentity {
guid: string;
name: string | null;
}
export interface BankAccount extends BankIdentity {
balance: number;
available: number;
pendingOperation: 'deposit' | 'withdraw' | null;
pendingAmount: number | null;
lastActivity: string | null;
lastAmount: number | null;
}
export interface BankTransfer extends BankIdentity {
id: number;
server: string;
game: string | null;
matchId: number | null;
matchAvailable: number;
amount: number;
balance: number;
createdAt: string;
}
export interface BankPending extends BankIdentity {
requestId: string;
server: string;
game: string | null;
matchId: number | null;
matchAvailable: number;
operation: 'deposit' | 'withdraw';
amount: number;
createdAt: string;
}
export interface BankPage<T> {
entries: T[];
total: number;
offset: number;
}
export interface BankOverview {
query: BankQuery;
maximum: number | null;
checkedAt: string;
summary: { totalBanked: number; fundedAccounts: number; transfers24h: number; pending: number };
servers: string[];
accounts: BankPage<BankAccount>;
transactions: BankPage<BankTransfer>;
pending: BankPage<BankPending>;
detail: {
account: BankAccount;
pending: BankPending | null;
history: BankPage<BankTransfer>;
} | null;
}