/** * The `dumps` table: its row type, its guard, the column lists services select * it with, and its mappers to and from the API's {@link Dump}. * * @module */ import type { SQLOutputValue } from "node:sqlite"; import type { Dump, RichContent } from "../../shared/dump.ts"; /** A row of the `dumps` table, with its live comment count. */ export interface DumpRow { /** The dump's id. */ id: string; /** `url` for a link, `file` for an upload. */ kind: string; /** The dump's title. */ title: string; /** The poster's description, in Markdown. */ comment: string | null; /** The poster's id. */ user_id: string; /** When the dump was posted. */ created_at: string; /** When the dump was last edited, if ever. */ updated_at: string | null; /** The dump's URL slug. */ slug: string | null; /** The link exactly as submitted, for a URL dump. */ url: string | null; /** The link's preview, as {@link RichContent} JSON. */ rich_content: string | null; /** The uploaded file's name, for a file dump. */ file_name: string | null; /** The uploaded file's type, for a file dump. */ file_mime: string | null; /** The uploaded file's size in bytes, for a file dump. */ file_size: number | null; /** How many users upvoted the dump. */ vote_count: number; /** How many comments it has, not counting deleted ones (computed). */ comment_count: number; /** 1 when only the poster sees the dump. */ is_private: number; /** The uploaded thumbnail's type, null without one. */ custom_thumbnail_mime: string | null; /** Any other column the query selects. */ [key: string]: SQLOutputValue; } /** * The stored columns of {@link DumpRow} — keep in sync with it and * {@link isDumpRow}. Services select with these instead of repeating the * column list themselves. */ export const DUMP_COLUMNS = "id, kind, title, slug, comment, user_id, created_at, updated_at, url, rich_content, file_name, file_mime, file_size, vote_count, is_private, custom_thumbnail_mime"; /** {@link DUMP_COLUMNS}, for a query aliasing `dumps` as `d`. */ export const DUMP_COLUMNS_ALIASED = "d.id, d.kind, d.title, d.slug, d.comment, d.user_id, d.created_at, d.updated_at, d.url, d.rich_content, d.file_name, d.file_mime, d.file_size, d.vote_count, d.is_private, d.custom_thumbnail_mime"; /** {@link DUMP_COLUMNS} and the computed `comment_count`: a full {@link DumpRow}. */ export const DUMP_SELECT_COLUMNS: string = `${DUMP_COLUMNS}, (SELECT COUNT(*) FROM comments WHERE dump_id = dumps.id AND deleted = 0) as comment_count`; /** {@link DUMP_SELECT_COLUMNS}, for a query aliasing `dumps` as `d`. */ export const DUMP_SELECT_COLUMNS_ALIASED: string = `${DUMP_COLUMNS_ALIASED}, (SELECT COUNT(*) FROM comments WHERE dump_id = d.id AND deleted = 0) as comment_count`; /** Checks whether `obj` is a {@link DumpRow}. */ export function isDumpRow(obj: unknown): obj is DumpRow { return !!obj && typeof obj === "object" && "id" in obj && typeof obj.id === "string" && "kind" in obj && typeof obj.kind === "string" && "title" in obj && typeof obj.title === "string" && "comment" in obj && (typeof obj.comment === "string" || obj.comment === null) && "user_id" in obj && typeof obj.user_id === "string" && "created_at" in obj && typeof obj.created_at === "string" && "updated_at" in obj && (typeof obj.updated_at === "string" || obj.updated_at === null) && "slug" in obj && (typeof obj.slug === "string" || obj.slug === null) && "url" in obj && (typeof obj.url === "string" || obj.url === null) && "rich_content" in obj && (typeof obj.rich_content === "string" || obj.rich_content === null) && "file_name" in obj && (typeof obj.file_name === "string" || obj.file_name === null) && "file_mime" in obj && (typeof obj.file_mime === "string" || obj.file_mime === null) && "file_size" in obj && (typeof obj.file_size === "number" || obj.file_size === null) && "vote_count" in obj && typeof obj.vote_count === "number" && "comment_count" in obj && typeof obj.comment_count === "number" && "is_private" in obj && typeof obj.is_private === "number" && "custom_thumbnail_mime" in obj && (typeof obj.custom_thumbnail_mime === "string" || obj.custom_thumbnail_mime === null); } /** Converts a row to the API's {@link Dump}. */ export function dumpRowToApi(row: DumpRow): Dump { return { id: row.id, kind: row.kind as "url" | "file", title: row.title, slug: row.slug ?? undefined, url: row.url ?? undefined, comment: row.comment ?? undefined, userId: row.user_id, createdAt: new Date(row.created_at), updatedAt: row.updated_at ? new Date(row.updated_at) : undefined, richContent: row.rich_content ? (JSON.parse(row.rich_content) as RichContent) : undefined, fileName: row.file_name ?? undefined, fileMime: row.file_mime ?? undefined, fileSize: row.file_size ?? undefined, voteCount: row.vote_count, commentCount: row.comment_count, isPrivate: Boolean(row.is_private), thumbnailMime: row.custom_thumbnail_mime ?? undefined, }; } /** Converts a {@link Dump} back to a row. */ export function dumpApiToRow(dump: Dump): DumpRow { return { id: dump.id, kind: dump.kind, title: dump.title, slug: dump.slug ?? null, comment: dump.comment ?? null, user_id: dump.userId, created_at: dump.createdAt.toISOString(), updated_at: dump.updatedAt?.toISOString() ?? null, url: dump.url ?? null, rich_content: dump.richContent ? JSON.stringify(dump.richContent) : null, file_name: dump.fileName ?? null, file_mime: dump.fileMime ?? null, file_size: dump.fileSize ?? null, vote_count: dump.voteCount, comment_count: dump.commentCount, is_private: dump.isPrivate ? 1 : 0, custom_thumbnail_mime: dump.thumbnailMime ?? null, }; }