v3: the docs follow deno doc's grain — the index page was a hand-kept list of some 250 links into deno doc's output layout (./api/main.ts/index.html), policed by mod.test.ts; deno doc has no module index of its own beyond all_symbols.html, which lists every module by path with its summary. mod.ts is now a prose overview of the three parts and their layers, linking that list once, and mod.test.ts is gone. Re-exporting everything from mod.ts, as deno doc's tutorial does for a library, was tried and doesn't fit: export * drops the 18 routers (default exports) and makes 17 names ambiguous, and deno 2.9 renders export * as namespaces with the root's module doc and nested ones empty, losing every module doc. A router's page showed one line: its endpoint list sat on the default symbol, whose summary was cut at the colon introducing it; the list is now the module doc of all 18 routers, and the router's JSDoc says so. 22 summaries ended in a colon (the routers, BandcampPlayer, migration 0016, useDumpListSync, usePlaylistListSync) and read cut short wherever deno doc shows a first paragraph alone; they end in a full stop, doc-lint.ts now fails on a colon summary, and the README states the rule. No behaviour changes; 204 backend tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
/**
|
||||
* The attachment routes, for images pasted into Markdown bodies.
|
||||
*
|
||||
* - `POST /api/attachments` — signed in. Stores an image (multipart `file`, at
|
||||
* most 5 MB) and answers its id; it is bound to a resource once a saved body
|
||||
* links it.
|
||||
* - `GET /api/attachments/:attachmentId` — public. Serves the image.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -19,14 +24,7 @@ import {
|
||||
validateImageUpload,
|
||||
} from "../lib/upload.ts";
|
||||
|
||||
/**
|
||||
* The attachment routes, for images pasted into Markdown bodies:
|
||||
*
|
||||
* - `POST /api/attachments` — signed in. Stores an image (multipart `file`, at
|
||||
* most 5 MB) and answers its id; it is bound to a resource once a saved body
|
||||
* links it.
|
||||
* - `GET /api/attachments/:attachmentId` — public. Serves the image.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router();
|
||||
|
||||
router.post("/api/attachments", authMiddleware, async (ctx) => {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/**
|
||||
* The avatar routes.
|
||||
*
|
||||
* - `POST /api/avatars/me` — signed in. Replaces the caller's avatar (multipart
|
||||
* `file`, at most 5 MB), announces it, and answers the caller's user.
|
||||
* - `GET /api/avatars/:userId` — public. Serves a user's avatar; 404 without
|
||||
* one.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -21,14 +26,7 @@ import {
|
||||
validateImageUpload,
|
||||
} from "../lib/upload.ts";
|
||||
|
||||
/**
|
||||
* The avatar routes:
|
||||
*
|
||||
* - `POST /api/avatars/me` — signed in. Replaces the caller's avatar (multipart
|
||||
* `file`, at most 5 MB), announces it, and answers the caller's user.
|
||||
* - `GET /api/avatars/:userId` — public. Serves a user's avatar; 404 without
|
||||
* one.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router();
|
||||
|
||||
router.post("/api/avatars/me", authMiddleware, async (ctx) => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* The Bandcamp route.
|
||||
* The Bandcamp route, for the native player.
|
||||
*
|
||||
* - `GET /api/bandcamp/tracks?url=&force=` — public. Resolves a Bandcamp page
|
||||
* to its streamable tracks, at play time; `force=1` bypasses the cache.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -9,12 +12,7 @@ import type { APIResponse } from "../../shared/api.ts";
|
||||
import { resolveBandcamp } from "../remote/bandcamp-streams.ts";
|
||||
import type { Tralbum } from "../../shared/player.ts";
|
||||
|
||||
/**
|
||||
* The Bandcamp route, for the native player:
|
||||
*
|
||||
* - `GET /api/bandcamp/tracks?url=&force=` — public. Resolves a Bandcamp page
|
||||
* to its streamable tracks, at play time; `force=1` bypasses the cache.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const bandcampRouter: Router = new Router();
|
||||
|
||||
// Public: it only reads a public page and returns URLs the same page already
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
/**
|
||||
* The category routes.
|
||||
*
|
||||
* - `GET /api/categories` — public. Answers every category, in order.
|
||||
* - `GET /api/categories/:slug` — public. Answers one category.
|
||||
* - `GET /api/categories/:slug/dumps?page=&limit=` — optional sign-in. Answers
|
||||
* a page of its dumps, newest first, with the caller's private ones.
|
||||
* - `POST /api/categories` — permission `category:manage`. Creates a category.
|
||||
* - `PUT /api/categories/:id` — permission `category:manage`. Updates a
|
||||
* category.
|
||||
* - `DELETE /api/categories/:id` — permission `category:manage`. Deletes a
|
||||
* category; its dumps stay.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -31,19 +41,7 @@ import {
|
||||
updateCategory,
|
||||
} from "../services/category-service.ts";
|
||||
|
||||
/**
|
||||
* The category routes:
|
||||
*
|
||||
* - `GET /api/categories` — public. Answers every category, in order.
|
||||
* - `GET /api/categories/:slug` — public. Answers one category.
|
||||
* - `GET /api/categories/:slug/dumps?page=&limit=` — optional sign-in. Answers
|
||||
* a page of its dumps, newest first, with the caller's private ones.
|
||||
* - `POST /api/categories` — permission `category:manage`. Creates a category.
|
||||
* - `PUT /api/categories/:id` — permission `category:manage`. Updates a
|
||||
* category.
|
||||
* - `DELETE /api/categories/:id` — permission `category:manage`. Deletes a
|
||||
* category; its dumps stay.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/categories" });
|
||||
|
||||
router.get("/", (ctx) => {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
/**
|
||||
* The chat history route.
|
||||
* The chat history route; messages are posted and edited over the socket (see
|
||||
* routes/ws.ts).
|
||||
*
|
||||
* - `GET /api/chat?limit=&before=&beforeId=&after=&afterId=` — signed in.
|
||||
* Answers messages in ascending order: the most recent by default; with
|
||||
* `before`, the page of older messages preceding that cursor ("load older");
|
||||
* with `after`, the messages following it (resync after a reconnect). A
|
||||
* cursor is an ISO `created_at` plus the message id to break ties; without
|
||||
* `afterId` the `after` timestamp itself is included.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -13,17 +21,7 @@ import {
|
||||
getRecentChatMessages,
|
||||
} from "../services/chat-service.ts";
|
||||
|
||||
/**
|
||||
* The chat history route; messages are posted and edited over the socket (see
|
||||
* routes/ws.ts):
|
||||
*
|
||||
* - `GET /api/chat?limit=&before=&beforeId=&after=&afterId=` — signed in.
|
||||
* Answers messages in ascending order: the most recent by default; with
|
||||
* `before`, the page of older messages preceding that cursor ("load older");
|
||||
* with `after`, the messages following it (resync after a reconnect). A
|
||||
* cursor is an ISO `created_at` plus the message id to break ties; without
|
||||
* `afterId` the `after` timestamp itself is included.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/chat" });
|
||||
|
||||
router.get("/", authMiddleware, (ctx) => {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
/**
|
||||
* The comment routes.
|
||||
* The comment routes. A private dump's comments exist only for its poster, and
|
||||
* changes to them aren't broadcast.
|
||||
*
|
||||
* - `GET /api/dumps/:dumpId/comments` — optional sign-in. Answers every comment
|
||||
* on a dump, oldest first.
|
||||
* - `POST /api/dumps/:dumpId/comments` — signed in. Posts a comment, replying
|
||||
* to `parentId` if given.
|
||||
* - `PATCH /api/comments/:commentId` — signed in, as its author or with
|
||||
* `comment:moderate`. Edits a comment.
|
||||
* - `GET /api/comments/:commentId/likers?page=&limit=` — optional sign-in.
|
||||
* Answers a page of the users who like a comment.
|
||||
* - `DELETE /api/comments/:commentId` — signed in, as its author or with
|
||||
* `comment:moderate`. Deletes a comment, leaving its place in the thread.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -37,21 +49,7 @@ import {
|
||||
broadcastCommentUpdated,
|
||||
} from "../services/ws-service.ts";
|
||||
|
||||
/**
|
||||
* The comment routes. A private dump's comments exist only for its poster, and
|
||||
* changes to them aren't broadcast:
|
||||
*
|
||||
* - `GET /api/dumps/:dumpId/comments` — optional sign-in. Answers every comment
|
||||
* on a dump, oldest first.
|
||||
* - `POST /api/dumps/:dumpId/comments` — signed in. Posts a comment, replying
|
||||
* to `parentId` if given.
|
||||
* - `PATCH /api/comments/:commentId` — signed in, as its author or with
|
||||
* `comment:moderate`. Edits a comment.
|
||||
* - `GET /api/comments/:commentId/likers?page=&limit=` — optional sign-in.
|
||||
* Answers a page of the users who like a comment.
|
||||
* - `DELETE /api/comments/:commentId` — signed in, as its author or with
|
||||
* `comment:moderate`. Deletes a comment, leaving its place in the thread.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api" });
|
||||
|
||||
router.get("/dumps/:dumpId/comments", async (ctx) => {
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
/**
|
||||
* The dump routes.
|
||||
* The dump routes. `:dumpId` takes an id or a slug; a private dump exists only
|
||||
* for its poster.
|
||||
*
|
||||
* - `POST /api/dumps` — signed in. Posts a link (JSON body) or a file
|
||||
* (multipart `file`, with `title`, `comment`, `isPrivate` and `categoryIds`).
|
||||
* - `GET /api/dumps/by-url?url=` — optional sign-in. Answers the dumps already
|
||||
* pointing at the same thing as `url`, for the create form's duplicate hint.
|
||||
* - `GET /api/dumps/:dumpId` — optional sign-in. Answers a dump.
|
||||
* - `GET /api/dumps/:dumpId/voters?page=&limit=` — optional sign-in. Answers a
|
||||
* page of the users who upvoted it.
|
||||
* - `GET /api/dumps/:dumpId/related` — optional sign-in. Answers the dumps
|
||||
* linked to or from it.
|
||||
* - `GET /api/dumps/:dumpId/playlists` — optional sign-in. Answers the
|
||||
* playlists it appears in.
|
||||
* - `GET /api/dumps?page=&limit=` — optional sign-in. Answers a page of the
|
||||
* dumps, newest first.
|
||||
* - `PUT /api/dumps/:dumpId/file` — signed in, as its poster or with
|
||||
* `dump:moderate`. Replaces a file dump's upload (multipart).
|
||||
* - `PUT /api/dumps/:dumpId` — signed in, as its poster or with
|
||||
* `dump:moderate`. Updates its title, comment, visibility, categories, and a
|
||||
* link's URL.
|
||||
* - `PUT /api/dumps/:dumpId/thumbnail` — signed in, as its poster or with
|
||||
* `dump:moderate`. Replaces its custom thumbnail (multipart `file`, at most 5
|
||||
* MB).
|
||||
* - `DELETE /api/dumps/:dumpId/thumbnail` — signed in, as its poster or with
|
||||
* `dump:moderate`. Removes its custom thumbnail.
|
||||
* - `POST /api/dumps/:dumpId/refresh-metadata` — signed in, as its poster or
|
||||
* with `dump:moderate`. Fetches a link's preview again.
|
||||
* - `DELETE /api/dumps/:dumpId` — signed in, as its poster or with
|
||||
* `dump:moderate`. Deletes it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -44,38 +73,7 @@ import { getDumpVoters } from "../services/vote-service.ts";
|
||||
import { getRelatedDumps } from "../services/backlink-service.ts";
|
||||
import { getPlaylistsForDump } from "../services/playlist-service.ts";
|
||||
|
||||
/**
|
||||
* The dump routes. `:dumpId` takes an id or a slug; a private dump exists only
|
||||
* for its poster:
|
||||
*
|
||||
* - `POST /api/dumps` — signed in. Posts a link (JSON body) or a file
|
||||
* (multipart `file`, with `title`, `comment`, `isPrivate` and `categoryIds`).
|
||||
* - `GET /api/dumps/by-url?url=` — optional sign-in. Answers the dumps already
|
||||
* pointing at the same thing as `url`, for the create form's duplicate hint.
|
||||
* - `GET /api/dumps/:dumpId` — optional sign-in. Answers a dump.
|
||||
* - `GET /api/dumps/:dumpId/voters?page=&limit=` — optional sign-in. Answers a
|
||||
* page of the users who upvoted it.
|
||||
* - `GET /api/dumps/:dumpId/related` — optional sign-in. Answers the dumps
|
||||
* linked to or from it.
|
||||
* - `GET /api/dumps/:dumpId/playlists` — optional sign-in. Answers the
|
||||
* playlists it appears in.
|
||||
* - `GET /api/dumps?page=&limit=` — optional sign-in. Answers a page of the
|
||||
* dumps, newest first.
|
||||
* - `PUT /api/dumps/:dumpId/file` — signed in, as its poster or with
|
||||
* `dump:moderate`. Replaces a file dump's upload (multipart).
|
||||
* - `PUT /api/dumps/:dumpId` — signed in, as its poster or with
|
||||
* `dump:moderate`. Updates its title, comment, visibility, categories, and a
|
||||
* link's URL.
|
||||
* - `PUT /api/dumps/:dumpId/thumbnail` — signed in, as its poster or with
|
||||
* `dump:moderate`. Replaces its custom thumbnail (multipart `file`, at most 5
|
||||
* MB).
|
||||
* - `DELETE /api/dumps/:dumpId/thumbnail` — signed in, as its poster or with
|
||||
* `dump:moderate`. Removes its custom thumbnail.
|
||||
* - `POST /api/dumps/:dumpId/refresh-metadata` — signed in, as its poster or
|
||||
* with `dump:moderate`. Fetches a link's preview again.
|
||||
* - `DELETE /api/dumps/:dumpId` — signed in, as its poster or with
|
||||
* `dump:moderate`. Deletes it.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/dumps" });
|
||||
|
||||
// Every route here takes a slug or an id; the services below them key on the
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* The route serving file dumps' uploads.
|
||||
*
|
||||
* - `GET /api/files/:dumpId?token=` — public; a private dump's file needs its
|
||||
* poster's media `token`. Serves the file, inline when that is safe and as a
|
||||
* download otherwise, with byte ranges.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -12,13 +16,7 @@ import { authenticateMedia } from "../lib/auth.ts";
|
||||
import { serveFile } from "../lib/serve-file.ts";
|
||||
import { dumpFileVersion, getDump } from "../services/dump-service.ts";
|
||||
|
||||
/**
|
||||
* The route serving file dumps' uploads:
|
||||
*
|
||||
* - `GET /api/files/:dumpId?token=` — public; a private dump's file needs its
|
||||
* poster's media `token`. Serves the file, inline when that is safe and as a
|
||||
* download otherwise, with byte ranges.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/files" });
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
/**
|
||||
* The follow routes.
|
||||
* The follow routes, all for what the caller follows.
|
||||
*
|
||||
* - `GET /api/follows/status` — signed in. Answers the ids of the users and
|
||||
* playlists they follow.
|
||||
* - `GET /api/follows/feed/users?page=&limit=&category=` — signed in. Answers a
|
||||
* page of the public dumps of the users they follow, newest first, optionally
|
||||
* in one category.
|
||||
* - `GET /api/follows/feed/playlists?page=&limit=&category=` — signed in.
|
||||
* Answers a page of the public dumps in the public playlists they follow,
|
||||
* most recently added first, optionally in one category.
|
||||
* - `POST /api/follows/users/:userId` — signed in. Follows a user.
|
||||
* - `DELETE /api/follows/users/:userId` — signed in. Stops following a user.
|
||||
* - `POST /api/follows/playlists/:playlistId` — signed in. Follows a public
|
||||
* playlist.
|
||||
* - `DELETE /api/follows/playlists/:playlistId` — signed in. Stops following a
|
||||
* playlist.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -38,24 +53,7 @@ function resolveCategoryFilter(
|
||||
/** The feed a filter naming no category yields. */
|
||||
const EMPTY_FEED: Page<Dump> = { items: [], hasMore: false };
|
||||
|
||||
/**
|
||||
* The follow routes, all for what the caller follows:
|
||||
*
|
||||
* - `GET /api/follows/status` — signed in. Answers the ids of the users and
|
||||
* playlists they follow.
|
||||
* - `GET /api/follows/feed/users?page=&limit=&category=` — signed in. Answers a
|
||||
* page of the public dumps of the users they follow, newest first, optionally
|
||||
* in one category.
|
||||
* - `GET /api/follows/feed/playlists?page=&limit=&category=` — signed in.
|
||||
* Answers a page of the public dumps in the public playlists they follow,
|
||||
* most recently added first, optionally in one category.
|
||||
* - `POST /api/follows/users/:userId` — signed in. Follows a user.
|
||||
* - `DELETE /api/follows/users/:userId` — signed in. Stops following a user.
|
||||
* - `POST /api/follows/playlists/:playlistId` — signed in. Follows a public
|
||||
* playlist.
|
||||
* - `DELETE /api/follows/playlists/:playlistId` — signed in. Stops following a
|
||||
* playlist.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/follows" });
|
||||
|
||||
// Static routes first to prevent Oak matching "status"/"feed" as a :param
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* The invite routes.
|
||||
*
|
||||
* - `POST /api/invites` — signed in. Makes an invite and answers its token.
|
||||
* - `GET /api/invites/:token` — public. Checks an invite is valid, before the
|
||||
* register page shows its form; 404 when it isn't.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -11,13 +15,7 @@ import type { InviteResponse } from "../../shared/user.ts";
|
||||
import { type AuthContext, authMiddleware } from "../middleware/auth.ts";
|
||||
import { createInvite, validateInvite } from "../services/invite-service.ts";
|
||||
|
||||
/**
|
||||
* The invite routes:
|
||||
*
|
||||
* - `POST /api/invites` — signed in. Makes an invite and answers its token.
|
||||
* - `GET /api/invites/:token` — public. Checks an invite is valid, before the
|
||||
* register page shows its form; 404 when it isn't.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/invites" });
|
||||
|
||||
router.post("/", authMiddleware, (ctx: AuthContext) => {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/**
|
||||
* The notification routes.
|
||||
* The notification routes, all for the caller's own notifications.
|
||||
*
|
||||
* - `GET /api/notifications?page=&limit=` — signed in. Answers a page of them,
|
||||
* newest first.
|
||||
* - `POST /api/notifications/read-all` — signed in. Marks them all read.
|
||||
* - `PATCH /api/notifications/:id/read` — signed in. Marks one read.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -20,14 +25,7 @@ import {
|
||||
markOneRead,
|
||||
} from "../services/notification-service.ts";
|
||||
|
||||
/**
|
||||
* The notification routes, all for the caller's own notifications:
|
||||
*
|
||||
* - `GET /api/notifications?page=&limit=` — signed in. Answers a page of them,
|
||||
* newest first.
|
||||
* - `POST /api/notifications/read-all` — signed in. Marks them all read.
|
||||
* - `PATCH /api/notifications/:id/read` — signed in. Marks one read.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/notifications" });
|
||||
|
||||
router.get("/", authMiddleware, (ctx: AuthContext) => {
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
/**
|
||||
* The playlist routes.
|
||||
* The playlist routes. `:playlistId` takes an id or a slug.
|
||||
*
|
||||
* - `GET /api/playlists/by-dump/:dumpId/memberships` — signed in. Answers each
|
||||
* of the caller's playlists and whether it holds the dump.
|
||||
* - `POST /api/playlists` — signed in. Creates a playlist.
|
||||
* - `GET /api/playlists/:playlistId` — optional sign-in. Answers a playlist
|
||||
* with the dumps the caller may see in it; a private one only to its owner.
|
||||
* - `PUT /api/playlists/:playlistId` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Updates its title, description and visibility.
|
||||
* - `DELETE /api/playlists/:playlistId` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Deletes it.
|
||||
* - `POST /api/playlists/:playlistId/dumps/:dumpId` — signed in, as its owner
|
||||
* or with `playlist:moderate`. Adds a dump at the top.
|
||||
* - `DELETE /api/playlists/:playlistId/dumps/:dumpId` — signed in, as its owner
|
||||
* or with `playlist:moderate`. Removes a dump.
|
||||
* - `POST /api/playlists/:playlistId/image` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Replaces its cover (multipart `file`, at most 5 MB).
|
||||
* - `GET /api/playlists/:playlistId/image?token=` — public; a private
|
||||
* playlist's cover needs its owner's media `token`. Serves its cover; 404
|
||||
* without one.
|
||||
* - `PUT /api/playlists/:playlistId/order` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Reorders the dumps the caller can see in it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -39,30 +60,7 @@ import {
|
||||
validateImageUpload,
|
||||
} from "../lib/upload.ts";
|
||||
|
||||
/**
|
||||
* The playlist routes. `:playlistId` takes an id or a slug:
|
||||
*
|
||||
* - `GET /api/playlists/by-dump/:dumpId/memberships` — signed in. Answers each
|
||||
* of the caller's playlists and whether it holds the dump.
|
||||
* - `POST /api/playlists` — signed in. Creates a playlist.
|
||||
* - `GET /api/playlists/:playlistId` — optional sign-in. Answers a playlist
|
||||
* with the dumps the caller may see in it; a private one only to its owner.
|
||||
* - `PUT /api/playlists/:playlistId` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Updates its title, description and visibility.
|
||||
* - `DELETE /api/playlists/:playlistId` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Deletes it.
|
||||
* - `POST /api/playlists/:playlistId/dumps/:dumpId` — signed in, as its owner
|
||||
* or with `playlist:moderate`. Adds a dump at the top.
|
||||
* - `DELETE /api/playlists/:playlistId/dumps/:dumpId` — signed in, as its owner
|
||||
* or with `playlist:moderate`. Removes a dump.
|
||||
* - `POST /api/playlists/:playlistId/image` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Replaces its cover (multipart `file`, at most 5 MB).
|
||||
* - `GET /api/playlists/:playlistId/image?token=` — public; a private
|
||||
* playlist's cover needs its owner's media `token`. Serves its cover; 404
|
||||
* without one.
|
||||
* - `PUT /api/playlists/:playlistId/order` — signed in, as its owner or with
|
||||
* `playlist:moderate`. Reorders the dumps the caller can see in it.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router<AuthState> = new Router<AuthState>({
|
||||
prefix: "/api/playlists",
|
||||
});
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/**
|
||||
* The link preview routes.
|
||||
*
|
||||
* - `GET /api/preview?url=` — signed in. Answers the preview of a link, and
|
||||
* whether its page could be reached.
|
||||
* - `GET /api/proxy-image?url=` — public. Serves a remote raster image through
|
||||
* this site; 400 when it isn't one, 502 when its host can't be reached.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -16,14 +21,7 @@ import {
|
||||
import type { LinkPreview } from "../../shared/dump.ts";
|
||||
import { authMiddleware } from "../middleware/auth.ts";
|
||||
|
||||
/**
|
||||
* The link preview routes:
|
||||
*
|
||||
* - `GET /api/preview?url=` — signed in. Answers the preview of a link, and
|
||||
* whether its page could be reached.
|
||||
* - `GET /api/proxy-image?url=` — public. Serves a remote raster image through
|
||||
* this site; 400 when it isn't one, 502 when its host can't be reached.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const previewRouter: Router = new Router();
|
||||
|
||||
// Signed-in only: it's the create form's preview, and an open one lets anyone
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* The search route.
|
||||
*
|
||||
* - `GET /api/search?q=&page=&limit=` — optional sign-in. Answers a page of the
|
||||
* matching dumps with their total, and up to 20 matching users and playlists;
|
||||
* private dumps and playlists only the caller's own.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -13,13 +17,7 @@ import { searchPlaylists } from "../services/playlist-service.ts";
|
||||
import type { APIResponse } from "../../shared/api.ts";
|
||||
import type { SearchResults } from "../../shared/search.ts";
|
||||
|
||||
/**
|
||||
* The search route:
|
||||
*
|
||||
* - `GET /api/search?q=&page=&limit=` — optional sign-in. Answers a page of the
|
||||
* matching dumps with their total, and up to 20 matching users and playlists;
|
||||
* private dumps and playlists only the caller's own.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/search" });
|
||||
|
||||
router.get("/", async (ctx) => {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* The route serving dumps' thumbnails, generated with ffmpeg where needed.
|
||||
*
|
||||
* - `GET /api/thumbnails/:dumpId?token=&v=` — public; a private dump's
|
||||
* thumbnail needs its poster's media `token`. Serves the dump's custom
|
||||
* thumbnail, else one made from its upload: a still of a video, or a
|
||||
* scaled-down copy of an image (the image itself when none can be made); 404
|
||||
* when there is neither.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -17,15 +23,7 @@ import { serveUploadedFile } from "../lib/upload.ts";
|
||||
import { DUMPS_DIR, THUMBNAILS_DIR } from "../config.ts";
|
||||
import { authenticateMedia } from "../lib/auth.ts";
|
||||
|
||||
/**
|
||||
* The route serving dumps' thumbnails:
|
||||
*
|
||||
* - `GET /api/thumbnails/:dumpId?token=&v=` — public; a private dump's
|
||||
* thumbnail needs its poster's media `token`. Serves the dump's custom
|
||||
* thumbnail, else one made from its upload: a still of a video, or a
|
||||
* scaled-down copy of an image (the image itself when none can be made); 404
|
||||
* when there is neither.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/thumbnails" });
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
/**
|
||||
* The user and account routes.
|
||||
* The user and account routes. Public profiles never carry the password hash or
|
||||
* the email.
|
||||
*
|
||||
* - `POST /api/users/register` — public, with a valid invite. Creates an
|
||||
* account, sends the welcome email, and answers the user and their tokens.
|
||||
* - `POST /api/users/login` — public. Answers the user and their tokens;
|
||||
* rate-limited per account.
|
||||
* - `GET /api/users/me` — signed in. Answers the caller's own user.
|
||||
* - `GET /api/users/me/media-token` — signed in. Answers a media token, for a
|
||||
* session that predates them.
|
||||
* - `POST /api/users/request-password-reset` — public. Emails a reset link to
|
||||
* the account with that address, if any; always answers 200.
|
||||
* - `POST /api/users/reset-password` — public, with a reset token. Sets a new
|
||||
* password.
|
||||
* - `POST /api/users/me/change-password` — signed in, with the current
|
||||
* password. Sets a new one, ending every other session, and answers fresh
|
||||
* tokens.
|
||||
* - `PATCH /api/users/me` — signed in. Updates the caller's profile fields:
|
||||
* description and email.
|
||||
* - `PATCH /api/users/:userId/role` — permission `user:manage`. Sets another
|
||||
* user's role.
|
||||
* - `GET /api/users/search?q=` — public. Answers up to 8 users whose name
|
||||
* starts with `q`, for @mention suggestions.
|
||||
* - `GET /api/users/by-id/:userId` — public. Answers a user's profile by id.
|
||||
* - `GET /api/users/:username/followed-users?page=&limit=` — public. Answers a
|
||||
* page of the users they follow.
|
||||
* - `GET /api/users/:username/followed-playlists?page=&limit=` — public.
|
||||
* Answers a page of the public playlists they follow.
|
||||
* - `GET /api/users/:username/playlists?page=&limit=` — optional sign-in.
|
||||
* Answers a page of their playlists, the private ones only to themselves.
|
||||
* - `GET /api/users/:username/invitees` — public. Answers the tree of users
|
||||
* they invited.
|
||||
* - `GET /api/users/:username` — public. Answers a user's profile.
|
||||
* - `GET /api/users/:username/dumps?page=&limit=` — optional sign-in. Answers a
|
||||
* page of their dumps, the private ones only to themselves.
|
||||
* - `GET /api/users/:username/votes?page=&limit=` — optional sign-in. Answers a
|
||||
* page of the dumps they upvoted, private ones only to themselves.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -70,45 +106,7 @@ import {
|
||||
getFollowedUsersByUser,
|
||||
} from "../services/follow-service.ts";
|
||||
|
||||
/**
|
||||
* The user and account routes. Public profiles never carry the password hash or
|
||||
* the email:
|
||||
*
|
||||
* - `POST /api/users/register` — public, with a valid invite. Creates an
|
||||
* account, sends the welcome email, and answers the user and their tokens.
|
||||
* - `POST /api/users/login` — public. Answers the user and their tokens;
|
||||
* rate-limited per account.
|
||||
* - `GET /api/users/me` — signed in. Answers the caller's own user.
|
||||
* - `GET /api/users/me/media-token` — signed in. Answers a media token, for a
|
||||
* session that predates them.
|
||||
* - `POST /api/users/request-password-reset` — public. Emails a reset link to
|
||||
* the account with that address, if any; always answers 200.
|
||||
* - `POST /api/users/reset-password` — public, with a reset token. Sets a new
|
||||
* password.
|
||||
* - `POST /api/users/me/change-password` — signed in, with the current
|
||||
* password. Sets a new one, ending every other session, and answers fresh
|
||||
* tokens.
|
||||
* - `PATCH /api/users/me` — signed in. Updates the caller's profile fields:
|
||||
* description and email.
|
||||
* - `PATCH /api/users/:userId/role` — permission `user:manage`. Sets another
|
||||
* user's role.
|
||||
* - `GET /api/users/search?q=` — public. Answers up to 8 users whose name
|
||||
* starts with `q`, for @mention suggestions.
|
||||
* - `GET /api/users/by-id/:userId` — public. Answers a user's profile by id.
|
||||
* - `GET /api/users/:username/followed-users?page=&limit=` — public. Answers a
|
||||
* page of the users they follow.
|
||||
* - `GET /api/users/:username/followed-playlists?page=&limit=` — public.
|
||||
* Answers a page of the public playlists they follow.
|
||||
* - `GET /api/users/:username/playlists?page=&limit=` — optional sign-in.
|
||||
* Answers a page of their playlists, the private ones only to themselves.
|
||||
* - `GET /api/users/:username/invitees` — public. Answers the tree of users
|
||||
* they invited.
|
||||
* - `GET /api/users/:username` — public. Answers a user's profile.
|
||||
* - `GET /api/users/:username/dumps?page=&limit=` — optional sign-in. Answers a
|
||||
* page of their dumps, the private ones only to themselves.
|
||||
* - `GET /api/users/:username/votes?page=&limit=` — optional sign-in. Answers a
|
||||
* page of the dumps they upvoted, private ones only to themselves.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router({ prefix: "/api/users" });
|
||||
|
||||
/** Login attempts per account: a burst of 10, then one every 30s. */
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* The WebSocket route, and the handling of what clients send over it.
|
||||
*
|
||||
* - `GET /ws?token=` — public; a session `token` signs the socket in, and an
|
||||
* invalid one leaves it anonymous. Opens the socket that carries presence,
|
||||
* live updates, votes, comment likes and the chat (see
|
||||
* `ClientToServerMessage` and `ServerToClientMessage` in shared/ws.ts); 403
|
||||
* from an origin not in {@link ALLOWED_ORIGINS}.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -56,15 +62,7 @@ import type {
|
||||
} from "../../shared/ws.ts";
|
||||
import type { User } from "../model/user.ts";
|
||||
|
||||
/**
|
||||
* The WebSocket route:
|
||||
*
|
||||
* - `GET /ws?token=` — public; a session `token` signs the socket in, and an
|
||||
* invalid one leaves it anonymous. Opens the socket that carries presence,
|
||||
* live updates, votes, comment likes and the chat (see
|
||||
* `ClientToServerMessage` and `ServerToClientMessage` in shared/ws.ts); 403
|
||||
* from an origin not in {@link ALLOWED_ORIGINS}.
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const router: Router = new Router();
|
||||
|
||||
/** Checks whether a socket may be opened from `origin`. */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* The YouTube route.
|
||||
* The YouTube route, for the player's tracklist.
|
||||
*
|
||||
* - `GET /api/youtube/playlist?list=` — public. Resolves a YouTube playlist id
|
||||
* to its tracklist (titles and lengths).
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -9,12 +12,7 @@ import type { APIResponse } from "../../shared/api.ts";
|
||||
import { resolveYouTubePlaylist } from "../remote/youtube-playlist.ts";
|
||||
import type { YouTubePlaylist } from "../../shared/player.ts";
|
||||
|
||||
/**
|
||||
* The YouTube route, for the player's tracklist:
|
||||
*
|
||||
* - `GET /api/youtube/playlist?list=` — public. Resolves a YouTube playlist id
|
||||
* to its tracklist (titles and lengths).
|
||||
*/
|
||||
/** The router serving the endpoints the module doc lists. */
|
||||
const youtubeRouter: Router = new Router();
|
||||
|
||||
// Public, like /api/bandcamp/tracks: it only reads a public page. The client
|
||||
|
||||
Reference in New Issue
Block a user