feat: integrate Hardcover API for books
- Add lib/hardcover.ts with GraphQL client for Hardcover API - Add routes/api/books/[name].ts for creating books via Hardcover ID - Add routes/api/books/enhance/[name].ts for enhancing books - Add routes/api/hardcover/query.ts for searching books - Add routes/books/[name].tsx and index.tsx for book pages
This commit is contained in:
75
routes/api/books/[name].ts
Normal file
75
routes/api/books/[name].ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { json } from "@lib/helpers.ts";
|
||||
import { getBookDetails } from "@lib/hardcover.ts";
|
||||
import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts";
|
||||
import { formatDate, safeFileName, toUrlSafeString } from "@lib/string.ts";
|
||||
import { AccessDeniedError } from "@lib/errors.ts";
|
||||
import { createResource, fetchResource } from "@lib/marka/index.ts";
|
||||
import { ReviewResource } from "@lib/marka/schema.ts";
|
||||
|
||||
export const handler: Handlers = {
|
||||
async GET(_, ctx) {
|
||||
const book = await fetchResource(`books/${ctx.params.name}`);
|
||||
return json(book?.content);
|
||||
},
|
||||
async POST(_, ctx) {
|
||||
const session = ctx.state.session;
|
||||
if (!session) throw new AccessDeniedError();
|
||||
|
||||
const hardcoverId = ctx.params.name;
|
||||
if (!hardcoverId) throw new AccessDeniedError();
|
||||
|
||||
const bookDetails = await getBookDetails(hardcoverId);
|
||||
|
||||
if (!bookDetails) {
|
||||
throw new Error("Book not found on Hardcover");
|
||||
}
|
||||
|
||||
const title = bookDetails.title || hardcoverId;
|
||||
const authorName = bookDetails.author_names?.[0] || "";
|
||||
const isbn = bookDetails.isbn13 || bookDetails.isbn || "";
|
||||
const releaseDate = bookDetails.release_year
|
||||
? `${bookDetails.release_year}-01-01`
|
||||
: undefined;
|
||||
|
||||
let finalPath = "";
|
||||
if (bookDetails.image) {
|
||||
try {
|
||||
const response = await fetch(bookDetails.image);
|
||||
if (response.ok) {
|
||||
const buffer = await response.arrayBuffer();
|
||||
const extension = fileExtension(bookDetails.image);
|
||||
finalPath = `books/images/${safeFileName(title)}_cover.${extension}`;
|
||||
await createResource(finalPath, buffer);
|
||||
}
|
||||
} catch {
|
||||
console.log("Failed to download book cover");
|
||||
}
|
||||
}
|
||||
|
||||
const book: ReviewResource["content"] = {
|
||||
_type: "Review",
|
||||
headline: title,
|
||||
subtitle: bookDetails.subtitle,
|
||||
bookBody: bookDetails.description || "",
|
||||
link: `https://hardcover.app/books/${bookDetails.slug}`,
|
||||
image: finalPath ? `resources/${finalPath}` : undefined,
|
||||
datePublished: formatDate(releaseDate),
|
||||
author: authorName ? {
|
||||
_type: "Person",
|
||||
name: authorName,
|
||||
} : undefined,
|
||||
itemReviewed: {
|
||||
name: title,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("Creating book resource:", JSON.stringify(book, null, 2));
|
||||
|
||||
const fileName = toUrlSafeString(title);
|
||||
|
||||
await createResource(`books/${fileName}.md`, book);
|
||||
|
||||
return json({ name: fileName });
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user