Add library push to website from the content editor

This commit is contained in:
SirBlob committed 2026-11-03 00:39:33 -05:00
1 parent ec57bd73b3
commit 0f90babb7a
13 files changed
+281 -20

No files matched your search

+25 -1
View File
@@ -2,7 +2,7 @@ import { error } from '@sveltejs/kit';
import type { Book, BookSeries, BookShelf } from '#lib/content';
import { timestamp, type SqlDatabase } from './database';
import { hasImage, imageUrl, removeImage, syncImage } from './images';
import { parseList, readChoice, readHistory, readNumber, readRating, readText, readTextList, type RequestBody } from './input';
import { parseList, readChoice, readHistory, readNumber, readRating, readText, readTextList, type ExportedEntry, type RequestBody } from './input';
interface BookRow {
goodreads_id: string;
@@ -74,6 +74,30 @@ export function listBooks(database: SqlDatabase, imageIds: Set<string>): Book[]
return rows.map((row) => toBook(row, imageIds.has(coverImageId(row.goodreads_id))));
}
export function exportBooks(database: SqlDatabase): ExportedEntry[] {
const rows = database.prepare('SELECT * FROM books ORDER BY goodreads_id').all() as BookRow[];
return rows.map((row) => ({
id: row.goodreads_id,
fields: {
title: row.title,
authors: parseList(row.authors),
description: row.description,
pages: row.pages,
published: row.published,
genres: parseList(row.genres),
averageRating: row.average_rating,
ratingCount: row.rating_count,
coverSource: row.cover_source,
shelf: row.shelf,
rating: row.rating,
series: row.series_name ? { name: row.series_name, url: row.series_url, position: row.series_position } : null,
added: row.added,
started: row.started,
finished: row.finished
}
}));
}
function readSeries(body: RequestBody, existing: BookRow | null): Pick<BookRow, 'series_name' | 'series_url' | 'series_position'> {
const current = {
series_name: existing?.series_name ?? null,
+31 -1
View File
@@ -3,7 +3,7 @@ import type { Game, PlayStatus } from '#lib/content';
import { likedLimit } from '#lib/library/gameProfile';
import { timestamp, type SqlDatabase } from './database';
import { hasImage, imageUrl, removeImage, syncImage } from './images';
import { parseList, readChoice, readFlag, readHistory, readNumber, readRating, readText, readTextList, type RequestBody } from './input';
import { parseList, readChoice, readFlag, readHistory, readNumber, readRating, readText, readTextList, type ExportedEntry, type RequestBody } from './input';
interface GameRow {
igdb_id: string;
@@ -77,6 +77,36 @@ export function listGames(database: SqlDatabase, imageIds: Set<string>): Game[]
return rows.map((row) => toGame(row, imageIds.has(gameImageId(row.igdb_id))));
}
export function exportGames(database: SqlDatabase): ExportedEntry[] {
const rows = database.prepare('SELECT * FROM games ORDER BY igdb_id').all() as GameRow[];
return rows.map((row) => ({
id: row.igdb_id,
fields: {
name: row.name,
url: row.store_url,
igdbUrl: row.igdb_url,
status: row.status,
rating: row.rating,
released: row.released,
platforms: parseList(row.platforms),
developers: parseList(row.developers),
publishers: parseList(row.publishers),
genres: parseList(row.genres),
summary: row.summary,
score: row.score,
imageSource: row.image_source,
coverSource: row.cover_source,
favorite: !!row.favorite,
liked: !!row.liked,
tags: parseList(row.tags),
note: row.note,
added: row.added,
started: row.started,
finished: row.finished
}
}));
}
function readList(body: RequestBody, key: string, current: string | undefined): string {
return JSON.stringify(readTextList(body, key, parseList(current ?? '[]')));
}
+15 -3
View File
@@ -1,12 +1,24 @@
import type { LibraryData } from '#lib/library/types';
import { libraryDatabase } from './database';
import { storedImageIds } from './images';
import { listMedia } from './media';
import { listBooks } from './books';
import { listGames } from './games';
import { exportMedia, listMedia } from './media';
import { exportBooks, listBooks } from './books';
import { exportGames, listGames } from './games';
import type { ExportedEntry } from './input';
export async function getLibrary(): Promise<LibraryData> {
const database = await libraryDatabase();
const imageIds = storedImageIds(database);
return { ...listMedia(database, imageIds), books: listBooks(database, imageIds), games: listGames(database, imageIds) };
}
export interface LibraryExport {
media: ExportedEntry[];
books: ExportedEntry[];
games: ExportedEntry[];
}
export async function exportLibrary(): Promise<LibraryExport> {
const database = await libraryDatabase();
return { media: exportMedia(database), books: exportBooks(database), games: exportGames(database) };
}
+5
View File
@@ -3,6 +3,11 @@ import { today } from './database';
export type RequestBody = Record<string, unknown>;
export interface ExportedEntry {
id: string;
fields: RequestBody;
}
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
export async function readBody(request: Request): Promise<RequestBody> {
+23 -1
View File
@@ -4,7 +4,7 @@ import type { MediaDetails, MediaKind } from '#lib/media/types';
import type { LibraryMedia } from '#lib/library/types';
import { timestamp, type SqlDatabase } from './database';
import { hasImage, imageUrl, removeImage, syncImage } from './images';
import { readChoice, readHistory, readRating, readText, type RequestBody } from './input';
import { readChoice, readHistory, readRating, readText, type ExportedEntry, type RequestBody } from './input';
interface MediaRow {
imdb_id: string;
@@ -83,6 +83,28 @@ export function listMedia(database: SqlDatabase, imageIds: Set<string>): Library
};
}
export function exportMedia(database: SqlDatabase): ExportedEntry[] {
const rows = database.prepare('SELECT * FROM media ORDER BY imdb_id').all() as MediaRow[];
return rows.map((row) => ({
id: row.imdb_id,
fields: {
kind: row.kind,
name: row.name,
status: row.status,
rating: row.rating,
collection: row.collection,
year: row.year,
genre: row.genre,
runtime: row.runtime,
plot: row.plot,
posterSource: row.poster_source,
added: row.added,
started: row.started,
finished: row.finished
}
}));
}
function mergeRow(imdbId: string, existing: MediaRow | null, body: RequestBody): MediaRow {
const name = readText(body, 'name', existing?.name ?? null);
if (!name) error(400, 'name is required');