API v2 Proposal
sup gamers,
I come from my inactivity to outline some improvements and refactoring for Gulag's API. Thanks to FastAPI, it is possible to implement some improvements that were much harder to do with cmyui_pkg. In my opinion, I think the current API (hereby referred to as v1) is should be deprecated and replaced with a v2. That being said, here are my takes for a future API.
In General
/apirate limit via X-Ratelimit (good stack overflow about it)- OAuth2 client credentials, authorization flow, and implicit flow
- OpenAPI specification (eh maybe / maybe not)
/apionly is 301 redirect to Rick Roll (le epic troll)- Response cache improvements (not building responses every time, etc)
- 10-second timeout
- returned datetime is always UTC rfc3339 i.e. 2021-12-06T04:49:25+00:00
- snake_case
- cursor objects embbed into search queries (semantics for later)
REST
Here are all v1 endpoints at the time of writing:
https://github.com/cmyui/gulag/blob/e40ed8921eb62a7561ecb6d5042e7062bb6b9a61/mount/app/api/domains/api.py#L45-L63
Proposed Changes
:mode = <osu | mania | catch | taiko>
GET /api/v2/online : return total registered & online player counts. (? I have to think about this one)
GET /api/v2/player/:id : return info or stats for a given player.
GET /api/v2/player/:id/status : return a player's current status, if online. (should be a WebSocket)
GET /api/v2/player/:id/scores/<best | recent | most_played> : return a list of best, recent, or most played scores for a given player.
GET /api/v2/map/:id : return information about a given beatmap
GET /api/v2/map/:id/:mode : return information about a given beatmap for a mode.
GET /api/v2/map/:id/:mode/scores : return the best scores for a given beatmap & mode.
GET /api/v2/mapset/:id : return information about a given beatmapset
GET /api/v2/mapset/:id/:mode : return information about a given beatmapset for a mode.
GET /api/v2/mapset/:id/:mode/scores : return the best scores for a given beatmap & mode.
GET /api/v2/score/:id : return information about a given score.
GET /api/v2/score/:id/dl: return the file for a given replay (with or without headers).
GET /api/v2/match/:id : return information for a given multiplayer match.
GET /api/v2/leaderboard/<score | pp>/:mode : return the top players for a given mode & sort condition
GET /api/v2/leaderboard/country/:country_code/<score | pp>/:mode
mode might be optional, just defaults to osu!std
GET /api/v2/map/:id/calc/pp : calculates pp
GET /api/v2/map/:id/calc/score : calculates largest possible score
GET /api/v2/me : user information about oneself
PUT /api/v2/me/avatar : upload an avatar
GET /api/v2/search/players : searches players
GET /api/v2/search/maps : searches maps
GET /api/v2/search/mapsets : searches mapsets
GET /api/v2/search/scores : searches scores scores
GET /api/v2/player/from/:name : returns a player response / id from a name
Reasoning
see osu!api v2
Get rid of "monopaths" and use a RESTful path system. Instead of a single endpoint returning a certain JSON response, every model should have child paths that return related responses. /get_user_info would become /user, /get_player_scores would become /player/scores, etc. The get_ prefix is redundant, as any request other than an HTTP GET should be a 405 error. _info endpoints can be moved to the root model path (i.e. /user, /beatmap, /score). When requesting models that have an Id (aka all of them), it would be better to pass their Id in the path (i.e /user/124493). In my mind path variables are required whereas query parameters are optional. Models should have two types of response structures: Compact and Full. The full structure is returned whenever the model is by itself, while compact is returned when it is in a list. Search endpoints should return a list of compact responses. If one needs more information one must request the full response.
Pros
- Easy to set up
- Everyone knows how to use a REST API
Cons
- Returns unused data (sometimes the client only wants a few fields from the returned data)
- Limited by number of endpoints
- Harder to Documnet
GraphQL
If you're unfamiliar, please take a look at the offical guide. It's a good read.
example query
query {
user(id: 124493) {
id
name
created_at
scores(type: RECENT) {
map {
title
}
pp
}
}
}
example response
{
"data": {
"user": {
"id": 124492,
"name": "chocomint",
"created_at": "2021-12-06T04:49:25+00:00",
"scores": [ // blue zenith joke haha so funny
{
"map": {
"title": "Blue Zenith"
}
"pp": 727.21
}
]
},
},
}
Reasoning
Imagine taking all the previous endpoints and condensing them into one: /graphql. Yep. Instead of having a path represent an entity, root queries that represent certain types of REST endpoints (in a way). For example, say we want to query a single user. We could have a query along the lines of user(id: 124493). Say we wanted multiple users. We can just do something like users(ids: [124493, 3]). This allows for much more flexibility. Also, no versioning!.
@cmyui I'd be happy to code up GraphQL. We can talk semantics on Discord.
Pros
- Much more flexibility
- More efficient data retrieval
- Better bulk requests (you can send files as Base64)
- Option of using
subscriptionfor WebSocket use (getting new scores via ws 💯 ) - Better development environment (GraphiQL)
Cons
- More difficult to setup (despite FastAPI having built-in support)
- Not many people know graphql
Final Notes
It's late, I'm tired, I'll keep this short. We should pick either REST or GraphQL. I think having both would be complicated: stick to one and do it well. I'm on side GraphQL. Ok bye.
