18 KiB
gerbeur's URLs
Every page and every API endpoint follows one of two rules. This file summarizes
them. Each router's module doc in api/routes/ lists its own endpoints in full,
and a route change updates both. shared/paths.ts builds and parses the page
paths.
The two rules
Pages. The path is a hierarchy. @name is a user, and @name@host a user
whose account lives on another instance. Any other word under the root is a
category or an app page. Under a user, each segment is a fixed tab name, and
under dumps or playlists, a slug. A view of a page is one more segment. A
default view is the bare path, with no redirect.
API. A resource is /api/<plural>/<key>[/<noun>]. Its key is its id, except
a user's, which may also be @name. The @ keeps the two apart, so no regex
sniffing is needed. A read by page path mirrors the page:
/api/users/@bob/dumps/<slug>. The caller's own things live under /api/me.
Media is a sub-resource. A lookup by another key is a query filter. Each concept
has one word, the same as in pages.
Pages
| Path | Views | Access | Page |
|---|---|---|---|
/ |
/hot, /new, /journal, /follow, /follow/playlists; the bare / is the viewer's default tab, the bare /follow the users followed |
anyone, /follow signed in |
Index |
/<category> |
/<category>/hot, /new, /journal, /follow, /follow/playlists |
anyone, /follow signed in |
Index |
/@<name> |
the overview: dumps beside upvoted | anyone | UserPublicProfile |
/@<name>/dumps, /upvoted, /playlists, /invitees |
anyone | UserPublicProfile |
|
/@<name>/follow |
/@<name>/follow/playlists; the bare tab lists users |
anyone | UserPublicProfile |
/@<name>/manage |
user:manage, on others' profiles |
UserPublicProfile |
|
/@<name>/dumps/<slug> |
anyone; a private dump, its poster | Dump |
|
/@<name>/dumps/<slug>/edit |
its poster, dump:moderate |
DumpEdit |
|
/@<name>/playlists/<slug> |
anyone; a private playlist, its owner | PlaylistDetail |
|
/@<name>/playlists/<slug>/edit |
its owner, playlist:moderate |
PlaylistDetail |
|
/search?q= |
/search/users?q=, /search/playlists?q= |
anyone | Search |
/notifications |
signed in | Notifications |
|
/community |
/community/monthly; the bare path is the week |
signed in | Community |
/settings |
signed in | Settings |
|
/manage |
category:manage, federation:manage |
Manage |
|
/login, /register?token= |
/login?next=<path> comes back to a page |
signed out | UserLogin, UserRegister |
/authorize?client_id=… |
signed in, else via /login?next= |
Authorize |
|
/federation/callback?code=&state= |
anyone | FederationCallback |
|
/reset-password |
/reset-password?token=, from the emailed link, sets the new password |
anyone | ResetPassword |
/unsubscribe?token= |
anyone | Unsubscribe |
/?share_url= opens the create form on a link shared from Android,
/?chat=open opens the chat over any page, and #comment-<id> scrolls a dump's
page to a comment.
Names
- Slugs of categories, dumps and playlists are lowercase letters and digits
in words joined by single hyphens (
shared/slug.ts). A category's is at most 50 characters, a dump's or a playlist's at most 60. - A dump's or a playlist's slug is made from its title when it is created,
unique among its owner's items of that kind: a clash gets
-2,-3. It changes only when someone edits it: the edit forms proposeslugify(newTitle)when the title changes. Its owner may change it 3 times a day; moderators (dump:moderate,playlist:moderate) may edit any slug, at any time. - A username is 1 to 32 letters, digits and underscores, unique in any case.
A page writes it in its stored case, and another case still resolves. Users
can't rename themselves: those with
user:managerename anyone, from/@<name>/manage. - A user from another instance is
name@hosthere: their name at home and their home's host, with its port if it has one (alice@music.example). Their name is their home's to change, which leaves the old handle an alias here too. Mentions write the whole handle. A handle with this instance's own host, as another instance writes a user from here (bob@gerbeur.example), names the localbob, in a path, an API key or a mention. - Aliases. A slug or a name left behind stays an alias for good, which always names the item or user itself, so redirects never chain. Nobody else can take one. An item can take back one of its own. Deleting an item frees its aliases.
- Redirects. A page reached by an alias, or by a name in another case, gets the item from the API under its current path and replaces its URL. The server answers the same path with a 301 to crawlers and full page loads.
- Reserved words. A category can't take a word the root already uses:
hot new journal follow search login register notifications reset-password unsubscribe settings manage community authorize federation api ws assets emojibase(api/lib/reserved-slugs.ts). Dump and playlist slugs reserve nothing, and no slug has an@.
API
Every JSON endpoint answers the envelope of shared/api.ts:
{ "success": true, "data": … }, or
{ "success": false, "error": { "code", "message" } } with the matching HTTP
status. A list answers { "items", "hasMore" } and takes ?page= (from 1) and
?limit= (20 by default, 100 at most). "Signed in" means a session token as
Authorization: Bearer …. Media can't send a header, so a private file,
thumbnail, waveform, cover or attachment takes the owner's media token as
?token=.
me
GET /api/me— signed in. The caller's own user.PATCH /api/me— signed in. Changes the description and email.GET /api/me/media-token— signed in. A media token.GET,PATCH /api/me/settings— signed in. The caller's settings.PUT /api/me/password— signed in, with the current one. A new password, and fresh tokens.PUT /api/me/avatar— signed in. A new avatar (multipart).GET /api/me/playlists?contains=:dumpId— signed in. The caller's playlists, and whether each holds the dump.GET /api/me/follow— signed in. The ids of the users and playlists followed.PUT,DELETE /api/me/follow/users/:id,/api/me/follow/playlists/:id— signed in. Follows or stops following.GET /api/me/notifications— signed in. The caller's notifications.POST /api/me/notifications/read— signed in. Marks them all read.GET /api/me/grants— signed in. The other instances the caller's account signs in to.DELETE /api/me/grants/:host— signed in. Withdraws one, ending the caller's sessions there.
An account from another instance changes its password, email, avatar and name there: those endpoints answer it 400.
users
POST /api/users— with an invite. Signs up.GET /api/users?q=— anyone. Up to 8 users whose name starts withq.GET /api/users/:key— anyone. A profile.GET /api/users/:key/avatar— anyone. The avatar.GET /api/users/:key/dumps,/upvoted,/playlists— anyone; private items to their owner only. Their lists.GET /api/users/:key/dumps/:slug,/playlists/:slug— anyone; private items to their owner only. An item by its page path, by an alias too.GET /api/users/:key/follow/users,/follow/playlists— anyone. What they follow.GET /api/users/:key/invitees— anyone. The tree of users they invited.GET /api/users/:key/home?path=— anyone. Redirects to a page of a remote user's home instance, their profile there by default.PATCH /api/users/:key/role,/username—user:manage. Another user's role, or anyone's name, theirs included.
dumps
POST /api/dumps— signed in. Posts a link (JSON) or a file (multipart).GET /api/dumps— anyone. The feed;?category=<slug>narrows it, and?url=answers the dumps of the same link instead.GET /api/dumps?follow=users|playlists— signed in. The feed'sfollowtab: the dumps of the users, or of the playlists, the caller follows;?category=narrows it too.GET /api/dumps/:id— anyone who may see it. A dump.GET /api/dumps/:id/voters,/related,/playlists— the same. Who upvoted it, the dumps linked to or from it, the playlists holding it.PUT /api/dumps/:id— its poster,dump:moderate. Title, slug, note, visibility, categories, link.POST /api/dumps/:id/metadata— the same. Fetches a link's preview again.DELETE /api/dumps/:id— the same. Deletes it.GET,PUT /api/dumps/:id/file— the file; a new one, by its poster ordump:moderate.GET,PUT,DELETE /api/dumps/:id/thumbnail— the thumbnail; a custom one, set or removed by its poster ordump:moderate.GET /api/dumps/:id/waveform— anyone who may see it. An audio file's waveform,{ "peaks" }, for the player to seek on.GET,POST /api/dumps/:id/comments— anyone who may see the dump; posting signed in. Its comments.
playlists
POST /api/playlists— signed in. Creates a playlist.GET /api/playlists/:id— anyone who may see it. A playlist and its dumps.PUT,DELETE /api/playlists/:id— its owner,playlist:moderate. Title, slug, description, visibility; deletion.POST,DELETE /api/playlists/:id/dumps/:dumpId— the same. Adds or removes a dump.PUT /api/playlists/:id/order— the same. Reorders its dumps.GET,PUT /api/playlists/:id/cover— the cover; a new one, by its owner orplaylist:moderate.
comments
PATCH,DELETE /api/comments/:id— its author,comment:moderate. Edits or deletes a comment.GET /api/comments/:id/likers— anyone. Who likes it.
categories
GET /api/categories— anyone. Every category.POST /api/categories,PUT,DELETE /api/categories/:id—category:manage. Changes them.
community
GET /api/community?period=weekly|monthly— signed in. The site's top dumps, new members, new public playlists and totals over the last week (by default) or month, as of the server's last refresh.
chat
GET /api/chat?before=&after=— signed in. A page of messages. The chat itself goes over/ws.- A message from a moderator or an admin (
chat:everyone) that says@everyonenotifies every user who doesn't have the chat open, and comes flaggedmentionsEveryone. No account may be namedeveryone.
invites
POST /api/invites— signed in. A new invite.GET /api/invites/:token— anyone. Whether it still works.
sessions, password-resets, unsubscribe
POST /api/sessions— anyone. Signs in.POST /api/password-resets— anyone. Emails a reset link.PUT /api/password-resets/:token— with the link's token. A new password.POST /api/unsubscribe?token=— with a digest's token. Turns it off. Its v3 path,POST /api/users/unsubscribe, stays for good, since sent emails'List-Unsubscribeheaders name it.
federation, oauth, .well-known
Federated sign-in (shared/federation.ts): a user signs in here with their
account on another gerbeur instance, their home, by OpenID Connect's
authorization code flow with PKCE, with tokens signed by each instance's Ed25519
key. With GERBEUR_FEDERATION_INCOMING=0, POST /api/federation/logins and
/sessions answer 403; with GERBEUR_FEDERATION_OUTGOING=0,
GET /api/oauth/clients and POST /api/oauth/codes do. An instance is named by
its issuer, its API's origin, whose host is the one in its users' handles. Where
instance is asked for, the origin of its pages works too: their
/.well-known/openid-configuration names the issuer, whose own metadata must
name those pages back as where its /authorize is.
GET /.well-known/openid-configuration,/.well-known/jwks.json— anyone. The instance's metadata and public keys, bare JSON: the one exception to the/api/rule, at the paths the standard fixes.POST /api/federation/logins— anyone. Starts a sign-in with an account on another instance; one neither trusted nor already home to someone here needs an invite.POST /api/federation/sessions— anyone. Finishes it, as a sign-in.POST /api/federation/events— another instance. Its signed news of one of its users here: a new name, email or avatar, or the end of their sessions. A 4xx tells it to give up on the event, a 429 or a 5xx to try again.GET /api/federation/peers— anyone the trusted instances,federation:manageevery listed one.PUT /api/federation/peers,DELETE /api/federation/peers/:host—federation:manage. Trusts or blocks an instance, or takes it off the lists.GET /api/oauth/clients?client_id=— signed in. The instance asking to sign the caller in.POST /api/oauth/codes— signed in. The caller's consent, and a code.POST /api/oauth/tokens— another instance, form-encoded. Trades a code for an identity token, bare JSON.
search
GET /api/search?q=— anyone. A page of dumps, and up to 20 users and playlists.
attachments
POST /api/attachments— signed in. Stores an image.GET /api/attachments/:id— anyone who may see the text holding it.
remote
GET /api/remote/preview?url=— signed in. A link's preview.GET /api/remote/image?url=— anyone. A remote image through this site.GET /api/remote/bandcamp?url=— anyone. A Bandcamp page's tracks.GET /api/remote/youtube?list=— anyone. A YouTube playlist's tracklist.
/ws
GET /ws?token=— anyone; a session token signs it in. The WebSocket of live updates and the chat (shared/ws.ts).