Skip to content

API for aggregated dashboards #1

Description

@gjuillot

Adding a collection overview page to altered.re

What I'm thinking about

I'd like to add a page where a user can see an overview of their collection — for example, to check whether they've got a full playset of a card or not. This would be the first of a few dashboards I have in mind. This first one has two views, drafted below:

Image Image
  • A faction × set matrix (first image): overall completion broken down by faction and set.
  • A per-card list (second image): one row per card, showing how many copies the user has of each rarity/version (e.g. 2/3 Common, 3/3 Rare…).

Another dashboard I'd like later: a completion view for people chasing every alternate art and Unique in a single faction.

Quick heads-up before the technical bits: I'm comfortable on the product/data side but I'm not an API expert, so treat what follows as "here's how I'm thinking about it" rather than a plan. Happy to be told I've got it wrong.

The problem

Right now there's no API to get aggregated numbers — like how many cards a user has in 0x, 1x, etc., with filters. We can only fetch all cards and update each counter.

The good news is that what the mockups need isn't open-ended — it's a fixed set of dimensions:

  • Faction (6) × Set (7) — the main matrix, 42 cells
  • Rarity / version — Common, Rare, Rare OOF, Exalted (toggleable)
  • Copy-count bucket — 0 / 1–2 / 3 / 4+
  • Some cards don't count toward a playset — Heroes, Uniques, alternatives

Scale: ~1,885 cards in the pool, everything is per-user, no cross-user stuff.

About our setup (correct me if I've got this wrong): the page is rendered by the PHP server, and the collection DB + API are on a separate server. So a request hops DB+API → PHP → browser. A user's whole collection is ~1,885 lines max; an aggregated result is ~42 cells plus a few numbers. Where we do the aggregation decides how far that big chunk of data has to travel.

The questions

Two separate things to figure out (plus one follow-up).

1. Where do we compute the aggregation?

Three places it could happen, since the page and the collection live on different servers:

On the DB+API server (right at the source).

  • Good: the full collection never leaves its server — only the small result travels. The DB does the GROUP BY, which is the thing it's good at. And the "what counts as a playset" rules stay next to the data.
  • Less good: needs a new API. And the aggregation ends up one network call away from the code that builds the page.

On the PHP server (while building the page).

  • Good: the computation sits right next to the page it's rendering, and the browser only gets the finished result. Rules live in one place.
  • Less good: PHP has to pull the whole collection over from the API every time it builds the page, and we'd be re-coding the grouping the DB already does for free.

In the browser, in JS.

  • Good: most freedom to change dashboards without touching the API or PHP.
  • Less good: the whole collection gets shipped all the way to the browser every load, we redo the grouping in JS, and the playset rules end up as far from the data as possible.

My gut says DB+API side — basically "let the API compute the numbers, let PHP/JS handle how they look." With two network hops in play, keeping the big data on its own server feels right. That said, the "keep the logic near the page" angle points at PHP-side, which I think is a fair second option if we don't want to touch the API yet. JS-side feels weakest to me. But you all know this better than I do — push back if I'm off.

2. If we go server-side: live or pre-computed?

(Assuming we land on DB+API or PHP for #1.)

  • Live — just run the GROUP BY on each request.
  • Pre-computed on a schedule — calculate every so often and store it. Downside: not real-time, so a user wouldn't see their change right away.
  • Pre-computed, updated on each change — keep counters and bump them whenever the collection changes.

My take: go live. The whole Player View is basically one GROUP BY (faction, set, rarity) for the user, with the matrix being a pivot of that and the dashboard tiles being roll-ups of the same thing. Since a user's data is small (a few thousand rows tops) and there's no cross-user aggregation, this should be fast without any caching.

The pre-computed options worry me more now that I know several dashboards are coming. Every new dashboard with a new dimension (price, acquisition date, deck usage…) would mean more stuff to pre-compute and keep in sync on every change. Live computing sidesteps that — a new dashboard is just another query. And if profiling later shows something's slow, we can add caching behind the API without changing anything for the page, so we're not locked in.

(If every future dashboard turned out to be just a re-slice of the same faction × set × rarity data, one stored "cube" could feed them all — but at this data size I think that's premature.)

3. Flexible endpoints or specific ones?

The question here is how much to bake in:

  • Flexible: a couple of generic endpoints with lots of query parameters (group_by, measures, filters). Future dashboards mostly reuse them — rarely a new endpoint.
  • Specific: narrowly-scoped endpoints with few parameters — two for this dashboard, more added as new dashboards come.

The tradeoff:

  • Flexible means less new code per dashboard, but looser control — we'd have to whitelist and validate hard, and each response contract is fuzzier (its shape depends on the params).
  • Specific means predictable, easy-to-validate, easy-to-optimize requests and self-documenting contracts, but more endpoints over time and some duplication.

My opinion: since the data's small and per-user, the performance reasons to favour tightly-scoped endpoints are weak, so for us this is mostly control vs. effort. I'd lean to starting specific (the two endpoints for this dashboard, lightly parameterised) and only generalising if we catch ourselves copy-pasting near-identical endpoints — rather than building a generic query layer up front for dashboards that don't exist yet.


That's where my head's at — but you all know this side far better than I do. Does the DB+API lean make sense to you, or am I missing something? Any concerns on load, the endpoint shape, or anything I've glossed over? Keen to hear what you think before I take this further.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions