Files
verse-quote-app/server/lib/quotes.ts
T

86 lines
2.3 KiB
TypeScript
Raw Normal View History

import { eq } from "drizzle-orm";
import { db, schema } from "../db/index.js";
export type PoemInfo = {
id: number;
title: string;
author: string;
source: string | null;
year: number | null;
};
export type QuoteWithPoem = {
id: number;
text: string;
tags: string[];
isUserSubmitted: boolean;
createdAt: string | null;
poem: PoemInfo;
favoriteCount: number;
isFavorited: boolean;
};
export function parseTags(tagsJson: string): string[] {
try {
const value = JSON.parse(tagsJson);
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
} catch {
return [];
}
}
type RawRow = {
quote: typeof schema.quotes.$inferSelect;
poem: typeof schema.poems.$inferSelect;
};
/**
* Joins quote rows with their poem, favorite counts, and (when visitorId is
* given) whether that visitor has favorited each quote. Shared by every
* action that returns quotes so the shape stays identical across list,
* random, favorites, and create.
*/
export async function hydrateQuotes(
rows: RawRow[],
visitorId?: string,
): Promise<QuoteWithPoem[]> {
if (rows.length === 0) return [];
const quoteIds = rows.map((r) => r.quote.id);
const allFavorites = await db().select().from(schema.favorites);
const countByQuoteId = new Map<number, number>();
const favoritedByVisitor = new Set<number>();
for (const fav of allFavorites) {
if (!quoteIds.includes(fav.quote_id)) continue;
countByQuoteId.set(fav.quote_id, (countByQuoteId.get(fav.quote_id) ?? 0) + 1);
if (visitorId && fav.visitor_id === visitorId) {
favoritedByVisitor.add(fav.quote_id);
}
}
return rows.map((row) => ({
id: row.quote.id,
text: row.quote.text,
tags: parseTags(row.quote.tags_json),
isUserSubmitted: row.quote.is_user_submitted,
createdAt: row.quote.created_at ?? null,
poem: {
id: row.poem.id,
title: row.poem.title,
author: row.poem.author,
source: row.poem.source ?? null,
year: row.poem.year ?? null,
},
favoriteCount: countByQuoteId.get(row.quote.id) ?? 0,
isFavorited: favoritedByVisitor.has(row.quote.id),
}));
}
export async function fetchAllQuoteRows(): Promise<RawRow[]> {
return db()
.select({ quote: schema.quotes, poem: schema.poems })
.from(schema.quotes)
.innerJoin(schema.poems, eq(schema.quotes.poem_id, schema.poems.id));
}