v4: API.md says what the URLs are again — it lacked the /playlists pages and GET /api/playlists, a playlist's categories, the ?category= of a user's lists and of the search, and ?chat=<message id>, and its reserved words lacked playlists. It now also states the rules the pages kept without saying: a category is a segment where the page is the category's own and ?category= where it narrows a list on another page; switching tabs takes the place of the history entry, while picking a category or opening another page or an editor adds one; a signed-in page opened signed out comes back after /login. canonical-url.ts named GET /api/dumps/by-url, which is GET /api/dumps?url=.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-09 22:25:08 +00:00
parent 71df0268e3
commit 1c5a449d84
3 changed files with 34 additions and 19 deletions

40
API.md
View File

@@ -11,7 +11,13 @@ paths.
whose account lives on another instance. Any other word under the root is a 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 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 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. 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. Switching tabs, or a tab's views, 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 **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 a user's, which may also be `@name`. The `@` keeps the two apart, so no regex
@@ -27,14 +33,15 @@ has one word, the same as in pages.
| `/` | `/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` | | `/` | `/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` | | `/<category>` | `/<category>/hot`, `/new`, `/journal`, `/follow`, `/follow/playlists` | anyone, `/follow` signed in | `Index` |
| `/@<name>` | the overview: dumps beside upvoted | anyone | `UserPublicProfile` | | `/@<name>` | the overview: dumps beside upvoted | anyone | `UserPublicProfile` |
| `/@<name>/dumps`, `/upvoted`, `/playlists`, `/invitees` | | anyone | `UserPublicProfile` | | `/@<name>/dumps`, `/upvoted`, `/playlists`, `/invitees` | `?category=` narrows the dumps, the upvoted or the playlists | anyone | `UserPublicProfile` |
| `/@<name>/follow` | `/@<name>/follow/playlists`; the bare tab lists users | anyone | `UserPublicProfile` | | `/@<name>/follow` | `/@<name>/follow/playlists`; the bare tab lists users | anyone | `UserPublicProfile` |
| `/@<name>/manage` | | `user:manage`, on others' profiles | `UserPublicProfile` | | `/@<name>/manage` | | `user:manage`, on others' profiles | `UserPublicProfile` |
| `/@<name>/dumps/<slug>` | | anyone; a private dump, its poster | `Dump` | | `/@<name>/dumps/<slug>` | | anyone; a private dump, its poster | `Dump` |
| `/@<name>/dumps/<slug>/edit` | | its poster, `dump:moderate` | `DumpEdit` | | `/@<name>/dumps/<slug>/edit` | | its poster, `dump:moderate` | `DumpEdit` |
| `/@<name>/playlists/<slug>` | | anyone; a private playlist, its owner | `PlaylistDetail` | | `/@<name>/playlists/<slug>` | | anyone; a private playlist, its owner | `PlaylistDetail` |
| `/@<name>/playlists/<slug>/edit` | | its owner, `playlist:moderate` | `PlaylistDetail` | | `/@<name>/playlists/<slug>/edit` | | its owner, `playlist:moderate` | `PlaylistDetail` |
| `/search?q=` | `/search/users?q=`, `/search/playlists?q=` | anyone | `Search` | | `/playlists` | `/playlists/<category>`, a playlist category's | anyone | `Playlists` |
| `/search?q=` | `/search/users?q=`, `/search/playlists?q=`; `?category=` narrows the dumps or the playlists | anyone | `Search` |
| `/notifications` | | signed in | `Notifications` | | `/notifications` | | signed in | `Notifications` |
| `/community` | `/community/monthly`; the bare path is the week | signed in | `Community` | | `/community` | `/community/monthly`; the bare path is the week | signed in | `Community` |
| `/settings` | | signed in | `Settings` | | `/settings` | | signed in | `Settings` |
@@ -45,9 +52,10 @@ has one word, the same as in pages.
| `/reset-password` | `/reset-password?token=`, from the emailed link, sets the new password | anyone | `ResetPassword` | | `/reset-password` | `/reset-password?token=`, from the emailed link, sets the new password | anyone | `ResetPassword` |
| `/unsubscribe?token=` | | anyone | `Unsubscribe` | | `/unsubscribe?token=` | | anyone | `Unsubscribe` |
`/?share_url=` opens the create form on a link shared from Android, `/?share_url=` opens the create form on a link shared from Android, `?chat=open`
`/?chat=open` opens the chat over any page, and `#comment-<id>` scrolls a dump's opens the chat over any page, `?chat=<message id>` on that message, and
page to a comment. `#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 ## Names
@@ -78,8 +86,8 @@ page to a comment.
answers the same path with a 301 to crawlers and full page loads. 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: - **Reserved words.** A category can't take a word the root already uses:
`hot new journal follow search login register notifications reset-password `hot new journal follow search login register notifications reset-password
unsubscribe settings manage community authorize federation api ws assets unsubscribe settings manage community playlists authorize federation api ws
emojibase` assets emojibase`
(`api/lib/reserved-slugs.ts`). Dump and playlist slugs reserve nothing, and no (`api/lib/reserved-slugs.ts`). Dump and playlist slugs reserve nothing, and no
slug has an `@`. slug has an `@`.
@@ -126,7 +134,8 @@ there: those endpoints answer it 400.
- `GET /api/users/:key` — anyone. A profile. - `GET /api/users/:key` — anyone. A profile.
- `GET /api/users/:key/avatar` — anyone. The avatar. - `GET /api/users/:key/avatar` — anyone. The avatar.
- `GET /api/users/:key/dumps`, `/upvoted`, `/playlists` — anyone; private items - `GET /api/users/:key/dumps`, `/upvoted`, `/playlists` — anyone; private items
to their owner only. Their lists. to their owner only. Their lists; `?category=` narrows one to a category of
its kind.
- `GET /api/users/:key/dumps/:slug`, `/playlists/:slug` — anyone; private items - `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. 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 - `GET /api/users/:key/follow/users`, `/follow/playlists` — anyone. What they
@@ -163,10 +172,13 @@ there: those endpoints answer it 400.
### playlists ### playlists
- `POST /api/playlists` — signed in. Creates a playlist. - `POST /api/playlists` — signed in. Creates a playlist, filed under the
playlist categories it names.
- `GET /api/playlists` — anyone. Every public playlist, newest first;
`?category=<slug>` narrows it to a playlist category.
- `GET /api/playlists/:id` — anyone who may see it. A playlist and its dumps. - `GET /api/playlists/:id` — anyone who may see it. A playlist and its dumps.
- `PUT`, `DELETE /api/playlists/:id` — its owner, `playlist:moderate`. Title, - `PUT`, `DELETE /api/playlists/:id` — its owner, `playlist:moderate`. Title,
slug, description, visibility; deletion. slug, description, visibility, categories; deletion.
- `POST`, `DELETE /api/playlists/:id/dumps/:dumpId` — the same. Adds or removes - `POST`, `DELETE /api/playlists/:id/dumps/:dumpId` — the same. Adds or removes
a dump. a dump.
- `PUT /api/playlists/:id/order` — the same. Reorders its dumps. - `PUT /api/playlists/:id/order` — the same. Reorders its dumps.
@@ -181,7 +193,8 @@ there: those endpoints answer it 400.
### categories ### categories
- `GET /api/categories` — anyone. Every category. - `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` — - `POST /api/categories`, `PUT`, `DELETE /api/categories/:id` —
`category:manage`. Changes them. `category:manage`. Changes them.
@@ -249,7 +262,8 @@ name those pages back as where its `/authorize` is.
### search ### search
- `GET /api/search?q=` — anyone. A page of dumps, and up to 20 users and - `GET /api/search?q=` — anyone. A page of dumps, and up to 20 users and
playlists. playlists. It finds both kinds of item at once, so `?category=` narrows the
dumps and `?playlistCategory=` the playlists.
### attachments ### attachments

View File

@@ -1,9 +1,9 @@
/** /**
* Spots reposts and links to known dumps, by reducing a URL to a key shared by * Spots reposts and links to known dumps, by reducing a URL to a key shared by
* every link to the same page or video. `dump-service` stores it in * every link to the same page or video. `dump-service` stores it in
* `dumps.url_canonical` when a dump is posted or edited, `GET * `dumps.url_canonical` when a dump is posted or edited, `GET /api/dumps?url=`
* /api/dumps/by-url` matches on it to warn the create form of a repost, and * matches on it to warn the create form of a repost, and backlink-service
* backlink-service compares links by it. The key is never fetched or shown, so * compares links by it. The key is never fetched or shown, so
* it may be lossy: it forces `https`, drops `www.`, tracking parameters and * it may be lossy: it forces `https`, drops `www.`, tracking parameters and
* timestamps. * timestamps.
* *

View File

@@ -3,9 +3,10 @@
* the path is a hierarchy, `@name` is a user (`@name@host` one whose account * the path is a hierarchy, `@name` is a user (`@name@host` one whose account
* lives on another instance), and any other word under the * lives on another instance), and any other word under the
* root is a dump category or an app page, `/playlists` holding the playlist * root is a dump category or an app page, `/playlists` holding the playlist
* categories. Under a user each segment is a fixed tab * categories. Under a user each segment is a fixed tab name, and under `dumps`
* name, and under `dumps` or `playlists` a slug. A view of a page is one more * or `playlists` a slug. A view of a page is one more segment, and a default
* segment, and a default view is the bare path. * view is the bare path. A category narrowing a list on another page, a
* profile's or a search's, is a query, `?category=`, not a segment.
* *
* The client links every page through the builders (`src/utils/shared/urls.ts` * The client links every page through the builders (`src/utils/shared/urls.ts`
* wraps the dump and playlist ones), the emails prefix them with the * wraps the dump and playlist ones), the emails prefix them with the