163 lines
6.0 KiB
TypeScript
163 lines
6.0 KiB
TypeScript
/**
|
|
* Turns the rows of `dumps` into the API's {@link Dump} the same way for every
|
|
* service that reads dumps: each selects them with the column lists here,
|
|
* checks each row with {@link isDumpRow} and converts it with {@link
|
|
* dumpRowToApi}.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import type { SQLOutputValue } from "node:sqlite";
|
|
import type { Dump, RichContent } from "../../shared/dump.ts";
|
|
|
|
/**
|
|
* Read from the `dumps` table with {@link DUMP_SELECT_COLUMNS}, or written back
|
|
* from a {@link Dump} by {@link dumpApiToRow}. It lacks `url_canonical`, which
|
|
* only the duplicate lookup reads.
|
|
*/
|
|
export interface DumpRow {
|
|
id: string;
|
|
kind: string;
|
|
title: string;
|
|
comment: string | null;
|
|
user_id: string;
|
|
created_at: string;
|
|
updated_at: string | null;
|
|
/** Unique among its poster's dumps, and set on every row since 0021. */
|
|
slug: string;
|
|
/** The link as the poster submitted it. */
|
|
url: string | null;
|
|
/** {@link RichContent} as JSON. */
|
|
rich_content: string | null;
|
|
file_name: string | null;
|
|
file_mime: string | null;
|
|
file_size: number | null;
|
|
vote_count: number;
|
|
/** Computed by a subquery, which counts the comments that aren't deleted. */
|
|
comment_count: number;
|
|
/** The poster's current name, computed by a subquery. */
|
|
owner_username: string;
|
|
is_private: number;
|
|
custom_thumbnail_mime: string | null;
|
|
/** Lets a query select more columns, such as a join's. */
|
|
[key: string]: SQLOutputValue;
|
|
}
|
|
|
|
/**
|
|
* Lists the stored columns of {@link DumpRow}. A column added to the table must
|
|
* be added here, to {@link DumpRow} and to {@link isDumpRow}.
|
|
*/
|
|
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";
|
|
|
|
// The same columns, for a query that aliases `dumps` as `d`.
|
|
const DUMP_COLUMNS_ALIASED = DUMP_COLUMNS.split(", ").map((c) => `d.${c}`)
|
|
.join(", ");
|
|
|
|
/**
|
|
* Selects a whole {@link DumpRow}, the stored columns and the computed
|
|
* `comment_count` and `owner_username`.
|
|
*/
|
|
export const DUMP_SELECT_COLUMNS: string = `${DUMP_COLUMNS},
|
|
(SELECT COUNT(*) FROM comments WHERE dump_id = dumps.id AND deleted = 0) as comment_count,
|
|
(SELECT username FROM users WHERE id = dumps.user_id) as owner_username`;
|
|
|
|
/**
|
|
* Selects a whole {@link DumpRow} in a query that aliases `dumps` as `d` to
|
|
* join it with other tables.
|
|
*/
|
|
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,
|
|
(SELECT username FROM users WHERE id = d.user_id) as owner_username`;
|
|
|
|
/**
|
|
* Checks whether `obj`, a row from a query, is a {@link DumpRow}. It checks
|
|
* each column's type, not that `kind` is `url` or `file`.
|
|
*/
|
|
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" &&
|
|
"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" &&
|
|
"owner_username" in obj && typeof obj.owner_username === "string" &&
|
|
"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}, parsing its preview's JSON. It
|
|
* leaves `categoryIds` out: the services add them with `attachCategoryIds`.
|
|
*/
|
|
export function dumpRowToApi(row: DumpRow): Dump {
|
|
return {
|
|
id: row.id,
|
|
kind: row.kind as "url" | "file",
|
|
title: row.title,
|
|
slug: row.slug,
|
|
url: row.url ?? undefined,
|
|
comment: row.comment ?? undefined,
|
|
userId: row.user_id,
|
|
ownerUsername: row.owner_username,
|
|
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, for a service that writes an edited
|
|
* dump's columns.
|
|
*/
|
|
export function dumpApiToRow(dump: Dump): DumpRow {
|
|
return {
|
|
id: dump.id,
|
|
kind: dump.kind,
|
|
title: dump.title,
|
|
slug: dump.slug,
|
|
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,
|
|
owner_username: dump.ownerUsername,
|
|
is_private: dump.isPrivate ? 1 : 0,
|
|
custom_thumbnail_mime: dump.thumbnailMime ?? null,
|
|
};
|
|
}
|