303 lines
20 KiB
Markdown
303 lines
20 KiB
Markdown
# 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. A category is a segment where
|
|
the page is the category's own, a dump category's feed at the root (`/music`)
|
|
and a playlist category's under `/playlists` (`/playlists/mixes`); where it
|
|
narrows a list on another page, a profile's or a search's, it is `?category=`,
|
|
of the kind that list holds. The order of a list of playlists is `?sort=`, the
|
|
default order being the bare path, and the playlists page's list of the viewer's
|
|
own is `?mine=1`. Switching tabs, a tab's views or a list's order takes the
|
|
place of the current history entry; picking a category, or opening another page
|
|
or an editor, adds one.
|
|
|
|
**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` | `?category=` narrows the dumps, the upvoted or the playlists; `?sort=` orders the playlists | 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` |
|
|
| `/playlists` | `/playlists/<category>`, a playlist category's; `?sort=new`, `?sort=dumps` order either; `?mine=1` lists the viewer's own, signed in only | anyone | `Playlists` |
|
|
| `/search?q=` | `/search/users?q=`, `/search/playlists?q=`; `?category=` narrows the dumps or the playlists | 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, `?chat=<message id>` on that message, and
|
|
`#comment-<id>` scrolls a dump's page to a comment. A signed-in page opened
|
|
signed out replaces itself with `/login?next=<the page>`.
|
|
|
|
## 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 propose `slugify(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:manage` rename anyone, from
|
|
`/@<name>/manage`.
|
|
- **A user from another instance** is `name@host` here: 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
|
|
local `bob`, 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 playlists 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=`.
|
|
|
|
An image the API serves, an avatar, a thumbnail, a cover, an attachment or a
|
|
remote image, takes `?w=` and answers a WebP copy scaled down to that width,
|
|
snapped up to one of `shared/image.ts`'s (128, 320, 640, 960), and never wider
|
|
than the image; without it, the image as it was uploaded, a dump's thumbnail
|
|
alone being scaled to the widest by default. `?f=compat` asks for the copy as a
|
|
JPEG or PNG, for a mail client that reads no WebP. An image no copy can be made
|
|
of goes out as it is. JSON answers of a kilobyte or more are compressed, in
|
|
brotli or gzip, for a client that accepts it.
|
|
|
|
### 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 with `q`.
|
|
- `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; `?category=` narrows one to a category of
|
|
its kind, and `?sort=` orders the playlists as `GET /api/playlists` does.
|
|
- `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's `follow` tab:
|
|
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 or
|
|
`dump:moderate`.
|
|
- `GET`, `PUT`, `DELETE /api/dumps/:id/thumbnail` — the thumbnail; a custom one,
|
|
set or removed by its poster or `dump: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, filed under the
|
|
playlist categories it names.
|
|
- `GET /api/playlists` — anyone. Every public playlist; `?category=<slug>`
|
|
narrows it to a playlist category, and `?sort=updated|new|dumps` orders it:
|
|
the last to take a dump first (the default), the newest, or the fullest. The
|
|
caller's own private dumps count, and date their playlists, for them alone.
|
|
- `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, categories; 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 or
|
|
`playlist: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, dump and playlist ones apart:
|
|
each has a `kind`, and its slug is unique within its kind.
|
|
- `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.
|
|
- `GET /api/community/feed` — signed in. The community feed: the 12 latest
|
|
things done in public, newest first (a public dump posted, a public playlist
|
|
created, a comment or an upvote on a public dump, someone joining), and the
|
|
last week's totals and most upvoted dump, as of the server's last refresh. A
|
|
dump has one comment event and one upvote event at most, its latest. Every
|
|
reader gets the same feed.
|
|
|
|
### 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 `@everyone`
|
|
notifies every user who doesn't have the chat open, and comes flagged
|
|
`mentionsEveryone`. No account may be named `everyone`.
|
|
|
|
### 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-Unsubscribe` headers 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:manage` every 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. It finds both kinds of item at once, so `?category=` narrows the
|
|
dumps and `?playlistCategory=` the 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`).
|