From 46a2e0aaf51aabb7b7e2df385565ba517a811084 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Wed, 26 Aug 2026 15:25:19 +0800 Subject: [PATCH] Add typed alertSearch API contract and response models without implementing the daily search flow. This prepares the app for endorser and partner alertSearch by capturing the known buckets, cursor ULID semantics, and JWT kinds, without changing notification scheduling or the background JWT pool. --- src/interfaces/alertSearch.ts | 125 ++++++++++++++++++++++++++++++++++ src/interfaces/index.ts | 1 + src/libs/alertSearch.ts | 67 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 src/interfaces/alertSearch.ts create mode 100644 src/libs/alertSearch.ts diff --git a/src/interfaces/alertSearch.ts b/src/interfaces/alertSearch.ts new file mode 100644 index 00000000..1ccf85a1 --- /dev/null +++ b/src/interfaces/alertSearch.ts @@ -0,0 +1,125 @@ +/** + * alertSearch response item and envelope types. + * + * These buckets are new to this app. Item shapes are taken from the endorser-ch + * SELECT lists for alertSearch, not from similarly named existing report types. + * + * Existing types that were considered and not reused: + * - GenericCredWrapper — claims lack a `claim` body; extra jwt columns differ. + * - GiveSummaryRecord / OfferSummaryRecord — those use `jwtId` and give/offer + * summary fields; alertSearch jwt rows use `id` and jwt table columns. + * - PlanSummaryAndPreviousClaim — `/plansLastUpdatedBetween` wraps `{ plan, + * wrappedClaimBefore }`; alertSearch `trackedPlanUpdates` are plan_claim rows. + * - PlanSummaryRecord — overlapping plan fields, but the app type is a subset + * (missing fulfillsLinkConfirmed, result*, etc.) and required fields differ. + * - UserProfile — partner nearby rows include `updatedAt` / `rowId` and omit + * embedding flags that UserProfile models. + */ + +/** + * Server-issued ULID on a stored JWT/plan record, used as alertSearch afterId / + * beforeId. Not an authentication JWT and not a delegated notification JWT. + */ +export type AlertSearchCursorUlid = string; + +/** + * JWT row from endorser `jwtsWithDidAfterId` (no claim body). + * Cursor field: `id`. + */ +export interface AlertSearchClaimRecord { + id: AlertSearchCursorUlid; + issuedAt: string; + issuer: string; + subject?: string; + claimType?: string; + handleId?: string; + fromEntity?: string; + toEntity?: string; +} + +/** + * JWT row from `jwtsForUserPlanContributions` and + * `jwtsGiveActionOfferForPlanHandleIds`. `claim` is the jwt table TEXT + * (canonical JSON string); alertSearch does not JSON.parse it. + * Cursor field: `id`. + */ +export interface AlertSearchJwtWithClaimRecord extends AlertSearchClaimRecord { + claim?: string; +} + +/** + * plan_claim row from `plansLastUpdatedBetween` and `plansByLocationAfterId`. + * Cursor field: `jwtId` (not `id`). + */ +export interface AlertSearchPlanRecord { + handleId: string; + jwtId: AlertSearchCursorUlid; + issuerDid?: string; + agentDid?: string; + fulfillsLinkConfirmed?: boolean | number; + fulfillsPlanClaimId?: string; + fulfillsPlanHandleId?: string; + name?: string; + description?: string; + image?: string; + endTime?: string; + startTime?: string; + locLat?: number; + locLon?: number; + resultDescription?: string; + resultIdentifier?: string; + url?: string; +} + +/** + * user_profile row from partner `profilesByLocationAfterDate`. + * Profiles have no JWT `id`; partner paging uses dates decoded from cursor ULIDs. + */ +export interface AlertSearchProfileRecord { + rowId?: number; + issuerDid: string; + updatedAt?: string; + description: string; + locLat?: number; + locLon?: number; + locLat2?: number; + locLon2?: number; +} + +export interface EndorserAlertSearchData { + claims: AlertSearchClaimRecord[]; + personalPlanContributions: AlertSearchJwtWithClaimRecord[]; + trackedPlanUpdates: AlertSearchPlanRecord[]; + trackedPlanClaims: AlertSearchJwtWithClaimRecord[]; + plansNearby: AlertSearchPlanRecord[]; +} + +export interface PartnerAlertSearchData { + profilesNearby: AlertSearchProfileRecord[]; +} + +/** + * Endorser GET/POST /api/v2/report/alertSearch body. + * Per-bucket SQL hitLimit is not currently copied onto this envelope. + * Timeouts may set `userMessage` instead. + */ +export interface EndorserAlertSearchResponse { + data: EndorserAlertSearchData; + userMessage?: string; +} + +/** + * Partner GET/POST /api/partner/alertSearch body. + */ +export interface PartnerAlertSearchResponse { + data: PartnerAlertSearchData; + userMessage?: string; +} + +/** + * Union of the six alertSearch buckets for a future combined daily run. + * Not returned by a single server endpoint today. + */ +export interface CombinedAlertSearchData + extends EndorserAlertSearchData, + PartnerAlertSearchData {} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 8197df86..9a02e735 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,3 +1,4 @@ +export * from "./alertSearch"; export * from "./claims"; export * from "./claims-result"; export * from "./common"; diff --git a/src/libs/alertSearch.ts b/src/libs/alertSearch.ts new file mode 100644 index 00000000..b1ecf5a5 --- /dev/null +++ b/src/libs/alertSearch.ts @@ -0,0 +1,67 @@ +/** + * Typed alertSearch API contract only. No HTTP client yet. + * + * Hosts: use DEFAULT_ENDORSER_API_SERVER and DEFAULT_PARTNER_API_SERVER from + * `@/constants/app`. Do not duplicate those constants here. + * + * JWT kinds (do not mix these): + * - Authentication JWT: short-lived access token (`iss`/`iat`/`exp`) sent as + * `Authorization: Bearer` for interactive API calls (`accessToken` / + * `getHeaders`). Identifies the requester DID. + * - Delegated notification JWT: tokens in the native background pool (~100 + * strings, `jti` + long `exp`) passed to daily-notification-plugin / + * notify-api so background workers can call Endorser. That pool is unchanged + * in this phase and is not an alertSearch cursor. + * - alertSearch cursor ULID: server-issued record/JWT primary id (26-char + * ULID). `afterId` means ids strictly greater than that ULID; `beforeId` + * means strictly less. First daily run omits afterId. `beforeId` is for + * pagination within a run. These are not auth JWTs and not pool tokens. + * + * Truncation: each endorser bucket query uses a server hit-limit (typically + * 50). That flag is not currently returned on the alertSearch JSON envelope. + * Timeouts may add `userMessage`. A later caller must still paginate with + * beforeId when a bucket may be incomplete. + */ + +import type { + AlertSearchCursorUlid, + CombinedAlertSearchData, + EndorserAlertSearchResponse, + PartnerAlertSearchResponse, +} from "@/interfaces/alertSearch"; + +export const ENDORSER_ALERT_SEARCH_PATH = "/api/v2/report/alertSearch"; +export const PARTNER_ALERT_SEARCH_PATH = "/api/partner/alertSearch"; + +export interface AlertSearchLocationBBox { + minLocLat: number; + maxLocLat: number; + minLocLon: number; + maxLocLon: number; +} + +/** + * Query/body params accepted by endorser and partner alertSearch (GET or POST). + * GET is the planned daily method; the server also accepts POST. + */ +export interface AlertSearchRequestParams { + afterId?: AlertSearchCursorUlid; + beforeId?: AlertSearchCursorUlid; + afterDate?: string; + beforeDate?: string; + location?: AlertSearchLocationBBox; + minLocLat?: number; + maxLocLat?: number; + minLocLon?: number; + maxLocLon?: number; + planHandleIds?: string[]; + planIds?: string[]; + handleIds?: string[]; +} + +export type { + AlertSearchCursorUlid, + CombinedAlertSearchData, + EndorserAlertSearchResponse, + PartnerAlertSearchResponse, +};