144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
export const afkPageSize = 25;
|
|
export const afkLedgerKinds = [
|
|
'free_grant',
|
|
'purchase',
|
|
'reward',
|
|
'reserve',
|
|
'start',
|
|
'release',
|
|
'unresolved',
|
|
'consume',
|
|
'correction'
|
|
] as const;
|
|
export type AfkLedgerKind = (typeof afkLedgerKinds)[number];
|
|
export type AfkSessionState = 'reserved' | 'started' | 'settled' | 'unresolved' | 'corrected';
|
|
|
|
export interface AfkQuery {
|
|
view: 'accounts' | 'sessions' | 'ledger';
|
|
q: string;
|
|
balance: '' | 'saved' | 'reserved' | 'unresolved';
|
|
sort: 'saved' | 'available' | 'recent';
|
|
game: '' | 't5' | 't6';
|
|
server: string;
|
|
state: '' | AfkSessionState;
|
|
kind: '' | AfkLedgerKind;
|
|
from: string;
|
|
to: string;
|
|
offset: number;
|
|
player: string;
|
|
}
|
|
|
|
export function readAfkQuery(params: URLSearchParams): AfkQuery {
|
|
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 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 offset = Number(params.get('offset') || 0);
|
|
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error('Invalid page offset.');
|
|
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', 'sessions', 'ledger'], 'accounts'),
|
|
q: text('q', 100),
|
|
balance: choice('balance', ['', 'saved', 'reserved', 'unresolved'], ''),
|
|
sort: choice('sort', ['saved', 'available', 'recent'], 'saved'),
|
|
game: choice('game', ['', 't5', 't6'], ''),
|
|
server: text('server', 256),
|
|
state: choice(
|
|
'state',
|
|
['', 'reserved', 'started', 'settled', 'unresolved', 'corrected'],
|
|
''
|
|
),
|
|
kind: choice('kind', ['', ...afkLedgerKinds], ''),
|
|
from,
|
|
to,
|
|
offset: Math.floor(offset / afkPageSize) * afkPageSize,
|
|
player: text('player', 128)
|
|
};
|
|
}
|
|
|
|
export interface AfkIdentity {
|
|
guid: string;
|
|
name: string | null;
|
|
}
|
|
export interface AfkAccount extends AfkIdentity {
|
|
freeSeconds: number;
|
|
savedSeconds: number;
|
|
availableSeconds: number;
|
|
reservedSeconds: number;
|
|
reservedSavedSeconds: number;
|
|
unresolvedSessions: number;
|
|
lastActivity: string | null;
|
|
freeRenewsAt: string | null;
|
|
}
|
|
export interface AfkSession extends AfkIdentity {
|
|
sessionId: string;
|
|
server: string;
|
|
game: string | null;
|
|
matchId: number | null;
|
|
matchAvailable: number;
|
|
state: AfkSessionState;
|
|
authorizedSeconds: number;
|
|
consumedSeconds: number | null;
|
|
chargedSeconds: number | null;
|
|
confirmedSeconds: number;
|
|
endReason: string | null;
|
|
createdAt: string;
|
|
startedAt: string | null;
|
|
endedAt: string | null;
|
|
}
|
|
export interface AfkLedgerEntry extends AfkIdentity {
|
|
id: number;
|
|
server: string;
|
|
game: string | null;
|
|
matchId: number | null;
|
|
matchAvailable: number;
|
|
kind: AfkLedgerKind;
|
|
freeDelta: number;
|
|
savedDelta: number;
|
|
freeSeconds: number;
|
|
savedSeconds: number;
|
|
operationId: string;
|
|
sessionId: string | null;
|
|
eventAt: string;
|
|
createdAt: string;
|
|
}
|
|
export interface AfkPage<T> {
|
|
entries: T[];
|
|
total: number;
|
|
offset: number;
|
|
}
|
|
export interface AfkOverview {
|
|
query: AfkQuery;
|
|
checkedAt: string;
|
|
capacitySeconds: number;
|
|
summary: {
|
|
accounts: number;
|
|
savedSeconds: number;
|
|
reservedSeconds: number;
|
|
unresolvedSessions: number;
|
|
};
|
|
servers: string[];
|
|
accounts: AfkPage<AfkAccount>;
|
|
sessions: AfkPage<AfkSession>;
|
|
ledger: AfkPage<AfkLedgerEntry>;
|
|
}
|