Skip to content

Latest commit

 

History

History
147 lines (112 loc) · 5.9 KB

File metadata and controls

147 lines (112 loc) · 5.9 KB

Context

Domain language used by the 7Cav API. The proto files (proto/milpacs.proto, proto/tickets.proto) are the contract; this document covers the concepts and any nuance that isn't obvious from reading the schema.

Source data

The API is a read layer over a XenForo forum's MySQL database, augmented by the NF Rosters add-on (which contributes the xf_nf_rosters_* tables that hold milpac records) and the Cav7/ApiKeyManager add-on (which contributes the API-key and scope tables). The API itself owns no schema; it queries upstream tables and maps them to its own proto types.

Milpac

A "milpac" is a member's military-personnel-record entry: rank, position, awards, service record, and the identifiers used to look them up across the wider 7Cav stack. MilpacService is the surface that serves them.

Profile shapes

A member's milpac is served in three shapes by different RPCs, each tuned to a known consumer:

  • Profile — full view: rank, positions, awards, records, and the connected-account identifiers.
  • LiteProfile — slim view: enough to render a roster row, without the per-member relational payload.
  • S1UniformsProfile — view for the uniforms tool; includes uniform-relevant fields and omits the rest.

The three are not subsets of one type; they are hand-mapped from the same upstream rows into distinct proto messages.

Roster and RosterType

A Roster is a collection of members grouped by unit, course, or status. RosterType is an enum identifying which roster is requested; its numeric values are used as the roster_id foreign key in the upstream tables. The three roster RPCs (GetRoster, GetLiteRoster, GetS1UniformsRoster) return the same set of members in the corresponding profile shape above.

Rank, Position, PositionGroup

A Rank is a pay-grade entry from the upstream rank catalog. A Position is an org-chart slot; positions are grouped into PositionGroups for hierarchical browsing. RankExpanded and PositionExpanded are the variants that include relational fields the plain message omits.

Record and Award

A Record is an entry on a member's service history (joins, promotions, transfers, etc.); RecordType enumerates the categories. An Award is a decoration entry.

AWOL

An entry on the AWOL list — members flagged as absent without leave. Served by GetAwol, used by status-tracking consumers.

Connected accounts

Members are looked up by 7Cav user id, by username, and by external account identifiers maintained by the forum's connected-account integrations:

  • Discord — Discord user id.
  • Gamertag — Xbox / PlayStation handle.
  • Keycloak — legacy SSO identifier. The Keycloak auth path has been removed; the lookup RPC is on the chopping block and should not be used in new code.

Tickets

TicketsService exposes the forum's ticket system (powered by the NF Tickets add-on) as a read-only API.

  • Ticket — a thread: title, status, category, participants, message count, timestamps. forum_url is populated when the API is configured with the public forum base URL.
  • Message — one post within a ticket, addressed by position (0-indexed within the thread).
  • Category — a top-level grouping for tickets; carries a current ticket count.
  • TicketParticipant — a member-to-ticket association with a role.

ListTicketMessages paginates with an opaque cursor whose semantic is "next position to include" (inclusive lower bound), so position=0 is reachable.

API key and scope

Clients authenticate with a Bearer token (case-insensitive prefix per RFC 7235). Tokens are issued by the forum admin UI, not by this service. Each token carries a set of named scopes. Current scopes:

  • read — gates the milpac surface (profiles, rosters, ranks, positions, AWOL).
  • read:tickets — gates the tickets surface.

Scope membership is checked per-handler; a token with read cannot read tickets, and vice versa.

SQL seam (integration-test harness)

testdb/ is the dockerized MariaDB harness — the "SQL seam" from PRD #112's testing decisions. testdb.Open(t) hands a test its own disposable database (forum-shaped schema + fixtures, embedded in the package) on a MariaDB 11.5 server; tests opt in via TESTDB_ADDR and skip without it. Run locally with make test-integration.

Two properties of the harness are load-bearing:

  • The schema deliberately omits the four indexes PRD #112 proposes, so "red" EXPLAIN plans stay reproducible (each test may CREATE INDEX in its own database to observe the flip).
  • The fixtures include a member whose milpac relation_id collides with another member's forum user_id (205), keeping the by-id profile route's frozen relation-key semantic testable.

Index script (PRD #112 Phase 1)

testdb/indexes.sql is the in-repo source of truth for the four indexes backing the hot read paths (composite user_id_post_date on xf_post serving two distinct aggregations — a loose index scan for the last-post aggregation and a covering index scan for the AWOL report's variant, whose extra MAX(post_id) disqualifies the loose scan; relation-id and user-id indexes on the rosters tables for the profile preloads). The EXPLAIN-plan tests in testdb/indexes_test.go pin each flip red→green: the unindexed schema must full-scan, the script must produce the loose scan, the covering scan, and index-backed preloads — a query or schema change that silently reintroduces a full scan fails a test, not a production latency budget.

The API never executes DDL. The script is applied manually by the DB admin (mysql xenforo < testdb/indexes.sql, human-gated in #122) and re-applied with the same one command after any forum add-on upgrade that rebuilds the tables (idempotent: ADD INDEX IF NOT EXISTS). The long-term home for re-application is the ApiKeyManager add-on's schema step (per PRD #112) — documented intent only, not implemented.