asobi_ops_controller (asobi v0.72.6)

View Source

HTTP surface of the ops plane: every read, plus the two account-lifecycle routes.

Every list endpoint here returns the same envelope, clamps its own paging, and rejects a sort field it does not recognise with 400 rather than ordering by something the caller did not ask for.

These routes are mounted behind asobi_ops_auth:verify/1, the operator capability check (ADR 0007) - never the player-scoped bearer check the rest of /api/v1 uses, which would let any authenticated player, guest included, enumerate the deployment. Every read projection here is still held to exactly what the equivalent public endpoint already exposes.

erase_player/1 and export_player/1 are the exceptions to that last sentence, deliberately: an operator answering a deletion or access request needs more than a player's own profile view. They are the only two routes on this plane that are not reads, they carry their own capability classes (erasure and player_data), and the erasure is audited inside its own transaction. See asobi_player_erase.

Summary

Types

response()

-type response() ::
          {json, map()} |
          {json, integer(), map(), map()} |
          {asobi_error, asobi_error:code()} |
          {asobi_error, asobi_error:code(), asobi_error:details()}.

Functions

chat_channels(Req)

-spec chat_channels(cowboy_req:req()) -> response().

chat_messages/1

-spec chat_messages(cowboy_req:req()) -> response().

economy_item(Req)

-spec economy_item(cowboy_req:req()) -> response().

economy_items(Req)

-spec economy_items(cowboy_req:req()) -> response().

economy_listing(Req)

-spec economy_listing(cowboy_req:req()) -> response().

economy_listings(Req)

-spec economy_listings(cowboy_req:req()) -> response().

erase_player/1

-spec erase_player(cowboy_req:req()) -> response().

Erase one player and everything core holds about them. Irreversible.

The body must echo the player's username, and the echo is checked against the row server-side. That guard is not distribution-shaped - the limitation it answers is a human clicking the wrong row, or a page being clickjacked into posting on the operator's behalf - so OTP does not dissolve it. It costs one comparison and makes an unattended POST insufficient.

export_player/1

-spec export_player(cowboy_req:req()) -> response().

Everything core holds about one player, as one JSON object.

player_data, not read: a leaderboard view is one thing, and the whole of one identified person's record is another.

features(Req)

-spec features(cowboy_req:req()) -> response().

leaderboard_entries/1

-spec leaderboard_entries(cowboy_req:req()) -> response().

leaderboards(Req)

-spec leaderboards(cowboy_req:req()) -> response().

match(Req)

-spec match(cowboy_req:req()) -> response().

matches(Req)

-spec matches(cowboy_req:req()) -> response().

matchmaker(Req)

-spec matchmaker(cowboy_req:req()) -> response().

notifications(Req)

-spec notifications(cowboy_req:req()) -> response().

player(Req)

-spec player(cowboy_req:req()) -> response().

players(Req)

-spec players(cowboy_req:req()) -> response().

stats(Req)

-spec stats(cowboy_req:req()) -> response().

tournament(Req)

-spec tournament(cowboy_req:req()) -> response().

tournaments(Req)

-spec tournaments(cowboy_req:req()) -> response().