/** * A dump is what users post: a link or a file, with a title and a note. This * module types it as the API sends it, with its link preview and the bodies * that create and edit it. The backend's `dump-service.ts` produces these types * and the frontend's dump pages and create form consume them. * * @module */ /** * Names the kind of link a {@link RichContent} previews: a site the player * plays its own way, or `generic` for any other page, this site's included. */ export type RichContentType = "youtube" | "bandcamp" | "soundcloud" | "generic"; /** * Built by a provider of `rich-content-service.ts` when a link is posted or * previewed, and stored with the dump as JSON. The frontend's * `RichContentCard` and player read it. */ export interface RichContent { /** The player picks how to play the link by it. */ type: RichContentType; url: string; siteName?: string; title?: string; description?: string; /** * A real preview image (og:image, a large content image, a video still). * Never a favicon — an absent value means "this page offers no artwork", * which is what makes the generated placeholder possible. */ thumbnailUrl?: string; /** Used as the placeholder's glyph. */ faviconUrl?: string; /** The page's declared brand color, normalized to `#rrggbb`. */ accentColor?: string; /** * The player a YouTube, Bandcamp or SoundCloud link plays in, framed by the * frontend. */ embedUrl?: string; } /** * Returned by every endpoint that answers dumps, and pushed to clients by the * `dump_created` and `dump_updated` socket messages. A URL dump has `url` and * `richContent`, a file dump the `file…` fields. A private dump reaches its * poster only. */ export interface Dump { id: string; kind: "url" | "file"; title: string; /** * Names the dump in its page's path, `dumpPath` (`shared/paths.ts`) building * it with {@link ownerUsername}. Made from the title on creation, it changes * only when edited, and is unique among its poster's dumps. */ slug: string; /** The poster's note on the dump, in Markdown. */ comment?: string; userId: string; /** The poster's current name, which the dump's path starts with. */ ownerUsername: string; createdAt: Date; updatedAt?: Date; url?: string; richContent?: RichContent; fileName?: string; fileMime?: string; fileSize?: number; voteCount: number; /** Deleted comments are not counted. */ commentCount: number; isPrivate: boolean; /** Set when the poster uploaded a custom thumbnail. */ thumbnailMime?: string; categoryIds?: string[]; } /** * Returned by `GET /api/dumps?url=` for each dump already posted with the * link being typed, and shown by the create form's `DuplicateNotice`. It holds * only what the notice displays and links to. */ export interface DumpUrlMatch { id: string; slug: string; title: string; /** The poster's username. */ ownerUsername: string; createdAt: Date; voteCount: number; commentCount: number; } /** * Returned by `GET /api/remote/preview` to the create form, which shows it before the * link is posted. */ export interface LinkPreview { /** * False when the page couldn't be fetched. `richContent` is then a stub * holding the host name, and this flag is how the form tells a dead link * from a page with no preview. */ reached: boolean; /** * Null only for a link that doesn't parse, which the route rejects before * fetching anyway. */ richContent: RichContent | null; } /** * Passed by the create form to `POST /api/dumps` as a JSON body, to post a * link. A file is posted as a multipart form instead. */ export interface CreateUrlDumpRequest { url: string; /** Replaces the title read from the page, when the poster edited it. */ title?: string; comment?: string; isPrivate?: boolean; categoryIds?: string[]; } /** * Passed to `PUT /api/dumps/:dumpId` by the edit page, and by the dump page's * inline title edit. A field left out stays as it is. */ export interface UpdateDumpRequest { /** Ignored for a file dump. A changed link has its preview fetched again. */ url?: string; title?: string; /** * A new slug, which the edit page's slug field sends. A title change never * changes the slug by itself. */ slug?: string; /** `null` removes the note. */ comment?: string | null; isPrivate?: boolean; /** Replaces the current categories. */ categoryIds?: string[]; } /** * Returns a file dump's version, which the file and thumbnail URLs carry as * `?v=`: the frontend's `dumpFileUrl` and `dumpThumbnailUrl`, and the digest's * thumbnails. It changes whenever the file may have, so the file and thumbnail * routes cache a URL naming the current one for good and revalidate any other. * Without its dates, a dump gets a version no route serves as current. */ export function dumpFileVersion( dump: { fileSize?: number; createdAt?: Date; updatedAt?: Date }, ): string { const at = dump.updatedAt ?? dump.createdAt; return `${dump.fileSize ?? 0}-${at ? at.getTime() : 0}`; }