mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
feat(web): add route loaders to prefetch dashboard, explore, and settings queries
This commit is contained in:
@@ -4,9 +4,8 @@ import { mkdir, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { BACKUP_DIR, DATABASE_URL } from "@sofa/config";
|
||||
import { closeDatabase } from "@sofa/db/client";
|
||||
import { closeDatabase, vacuumDatabase } from "@sofa/db/client";
|
||||
import { runMigrations } from "@sofa/db/migrate";
|
||||
import { vacuumInto } from "@sofa/db/utils";
|
||||
import { createLogger } from "@sofa/logger";
|
||||
|
||||
function formatTimestamp(date: Date): string {
|
||||
@@ -153,7 +152,7 @@ async function createBackupInternal(prefix: BackupPrefix): Promise<BackupInfo> {
|
||||
const filename = `${prefix}-${timestamp}.db`;
|
||||
const dest = path.join(BACKUP_DIR, filename);
|
||||
|
||||
vacuumInto(dest);
|
||||
vacuumDatabase(dest);
|
||||
|
||||
const s = await Bun.file(dest).stat();
|
||||
log.info(`Created backup: ${filename} (${s.size} bytes)`);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"./migrate": "./src/migrate.ts",
|
||||
"./schema": "./src/schema.ts",
|
||||
"./test-utils": "./src/test-utils.ts",
|
||||
"./utils": "./src/utils.ts",
|
||||
"./queries/*": "./src/queries/*.ts"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -38,6 +38,10 @@ function getClient() {
|
||||
globalForDb._client.run("PRAGMA journal_mode = WAL");
|
||||
globalForDb._client.run("PRAGMA foreign_keys = ON");
|
||||
globalForDb._client.run("PRAGMA busy_timeout = 5000");
|
||||
globalForDb._client.run("PRAGMA synchronous = NORMAL");
|
||||
globalForDb._client.run("PRAGMA cache_size = -64000");
|
||||
globalForDb._client.run("PRAGMA temp_store = MEMORY");
|
||||
globalForDb._client.run("PRAGMA mmap_size = 268435456");
|
||||
}
|
||||
return globalForDb._client;
|
||||
}
|
||||
@@ -59,6 +63,15 @@ export const db = new Proxy({} as ReturnType<typeof drizzle>, {
|
||||
},
|
||||
});
|
||||
|
||||
/** Run PRAGMA optimize to refresh query planner statistics. */
|
||||
export function optimizeDatabase() {
|
||||
getClient().run("PRAGMA optimize");
|
||||
}
|
||||
|
||||
export function vacuumDatabase(into: string): void {
|
||||
getClient().run("VACUUM INTO ?", [into.replace(/'/g, "''")]);
|
||||
}
|
||||
|
||||
/** Close the current connection, and clear singletons so the Proxy re-initializes on next access. */
|
||||
export function closeDatabase() {
|
||||
globalForDb._client?.close();
|
||||
|
||||
@@ -170,14 +170,7 @@ export function getLibraryFeed(userId: string, page = 1, limit = 20) {
|
||||
eq(userTitleStatus.userId, userId),
|
||||
);
|
||||
|
||||
const [{ count }] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(titles)
|
||||
.innerJoin(userTitleStatus, joinCondition)
|
||||
.where(availabilityFilter)
|
||||
.all();
|
||||
|
||||
const items = db
|
||||
const rows = db
|
||||
.select({
|
||||
titleId: titles.id,
|
||||
title: titles.title,
|
||||
@@ -190,6 +183,7 @@ export function getLibraryFeed(userId: string, page = 1, limit = 20) {
|
||||
voteAverage: titles.voteAverage,
|
||||
popularity: titles.popularity,
|
||||
userStatus: userTitleStatus.status,
|
||||
totalCount: sql<number>`count(*) over()`.as("totalCount"),
|
||||
})
|
||||
.from(titles)
|
||||
.innerJoin(userTitleStatus, joinCondition)
|
||||
@@ -199,7 +193,17 @@ export function getLibraryFeed(userId: string, page = 1, limit = 20) {
|
||||
.offset(offset)
|
||||
.all();
|
||||
|
||||
const totalResults = count ?? 0;
|
||||
let totalResults = rows[0]?.totalCount ?? 0;
|
||||
if (rows.length === 0 && offset > 0) {
|
||||
const [{ count }] = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(titles)
|
||||
.innerJoin(userTitleStatus, joinCondition)
|
||||
.where(availabilityFilter)
|
||||
.all();
|
||||
totalResults = count ?? 0;
|
||||
}
|
||||
const items = rows.map(({ totalCount: _, ...item }) => item);
|
||||
return {
|
||||
items,
|
||||
page,
|
||||
|
||||
@@ -275,49 +275,34 @@ export function getEpisodeProgressByTitleIds(userId: string, titleIds: string[])
|
||||
}
|
||||
|
||||
export function getUserTitleInfo(userId: string, titleId: string) {
|
||||
const status = db
|
||||
.select()
|
||||
const info = db
|
||||
.select({
|
||||
status: userTitleStatus.status,
|
||||
ratingStars: userRatings.ratingStars,
|
||||
})
|
||||
.from(userTitleStatus)
|
||||
.leftJoin(
|
||||
userRatings,
|
||||
and(
|
||||
eq(userRatings.userId, userTitleStatus.userId),
|
||||
eq(userRatings.titleId, userTitleStatus.titleId),
|
||||
),
|
||||
)
|
||||
.where(and(eq(userTitleStatus.userId, userId), eq(userTitleStatus.titleId, titleId)))
|
||||
.get();
|
||||
|
||||
const rating = db
|
||||
.select()
|
||||
.from(userRatings)
|
||||
.where(and(eq(userRatings.userId, userId), eq(userRatings.titleId, titleId)))
|
||||
.get();
|
||||
|
||||
const allEps = db
|
||||
.select({ id: episodes.id })
|
||||
.from(episodes)
|
||||
const watchedEpisodeIds = db
|
||||
.selectDistinct({ episodeId: userEpisodeWatches.episodeId })
|
||||
.from(userEpisodeWatches)
|
||||
.innerJoin(episodes, eq(userEpisodeWatches.episodeId, episodes.id))
|
||||
.innerJoin(seasons, eq(episodes.seasonId, seasons.id))
|
||||
.where(eq(seasons.titleId, titleId))
|
||||
.all();
|
||||
|
||||
const epIds = allEps.map((ep) => ep.id);
|
||||
|
||||
const watchedEpisodeIds =
|
||||
epIds.length > 0
|
||||
? Array.from(
|
||||
new Set(
|
||||
db
|
||||
.select({ episodeId: userEpisodeWatches.episodeId })
|
||||
.from(userEpisodeWatches)
|
||||
.where(
|
||||
and(
|
||||
eq(userEpisodeWatches.userId, userId),
|
||||
inArray(userEpisodeWatches.episodeId, epIds),
|
||||
),
|
||||
)
|
||||
.all()
|
||||
.map((w) => w.episodeId),
|
||||
),
|
||||
)
|
||||
: [];
|
||||
.where(and(eq(userEpisodeWatches.userId, userId), eq(seasons.titleId, titleId)))
|
||||
.all()
|
||||
.map((w) => w.episodeId);
|
||||
|
||||
return {
|
||||
status: status?.status ?? null,
|
||||
rating: rating?.ratingStars ?? null,
|
||||
status: info?.status ?? null,
|
||||
rating: info?.ratingStars ?? null,
|
||||
episodeWatches: watchedEpisodeIds,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
import { db } from "./client";
|
||||
|
||||
export function vacuumInto(destPath: string): void {
|
||||
db.run(sql.raw(`VACUUM INTO '${destPath.replace(/'/g, "''")}'`));
|
||||
}
|
||||
Reference in New Issue
Block a user