wip: implement bookmark preview

This commit is contained in:
2025-05-08 15:51:17 +01:00
parent 77cb38294c
commit 9b47b806d5
10 changed files with 164 additions and 58 deletions

View File

@@ -7,12 +7,13 @@ import { HttpError } from "~/error.ts"
import type { User } from "~/user/user.ts"
import { JSDOM } from "jsdom"
import { Readability } from "@mozilla/readability"
import { findBookmarkHtml } from "./bookmark.ts"
const BOOKMARK_PAGINATION_LIMIT = 100
const ListUserBookmarksParams = type({
limit: ["number", "=", BOOKMARK_PAGINATION_LIMIT],
skip: ["number", "=", 5],
skip: ["number", "=", 0],
})
const AddBookmarkRequestBody = type({
@@ -31,11 +32,10 @@ async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user
const listBookmarksQuery = db.query(
`
SELECT bookmarks.id, bookmarks.kind, bookmarks.title, bookmarks.url, tags.name as tag FROM bookmarks
LEFT JOIN tags
ON bookmarks.id = tags.bookmark_id
SELECT bookmarks.id, bookmarks.kind, bookmarks.title, bookmarks.url FROM bookmarks
WHERE bookmarks.user_id = $userId
ORDER BY bookmarks.id LIMIT $limit OFFSET $skip
ORDER BY bookmarks.id DESC
LIMIT $limit OFFSET $skip
`,
)
@@ -67,26 +67,47 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User
}
const websiteResponse = await fetch(body.url).catch(() => {
if (body.force) {
return null
}
throw new HttpError(400, "WebsiteUnreachable")
})
const websiteText = await websiteResponse.text().catch(() => {
throw new HttpError(400, "UnsupportedWebsite")
})
const dom = new JSDOM(websiteText, {
url: body.url,
})
const reader = new Readability(dom.window.document)
const article = reader.parse()
const websiteText = websiteResponse
? await websiteResponse.text().catch(() => {
throw new HttpError(400, "UnsupportedWebsite")
})
: null
const bookmark: LinkBookmark = {
kind: "link",
id: ulid(),
title: body.title || article?.title || "Untitled",
title: "",
url: body.url,
tags: body.tags.map((tag) => ({ id: ulid(), name: tag })),
}
if (body.title) {
bookmark.title = body.title
}
let contentHtml: string
if (websiteText) {
const dom = new JSDOM(websiteText, {
url: body.url,
})
const reader = new Readability(dom.window.document)
const article = reader.parse()
if (!bookmark.title) {
bookmark.title = article?.title || "Untitled"
}
contentHtml = article?.content || ""
} else {
contentHtml = ""
if (!bookmark.title) {
bookmark.title = "Untitled"
}
}
const query = db.query(`
INSERT INTO bookmarks (id, user_id, kind, title, url, content_html)
VALUES ($id, $userId, $kind, $title, $url, $html)
@@ -97,7 +118,7 @@ VALUES ($id, $userId, $kind, $title, $url, $html)
kind: bookmark.kind,
title: bookmark.title,
url: bookmark.url,
html: article?.content ?? websiteText,
html: contentHtml,
})
const insertTagQuery = db.query(`
@@ -122,4 +143,23 @@ VALUES ($id, $bookmarkId, $name)
return Response.json(undefined, { status: 204 })
}
export { addBookmark, listUserBookmarks, deleteUserBookmark }
async function fetchBookmark(request: Bun.BunRequest<"/api/bookmark/:id">, user: User) {
switch (request.headers.get("Accept")) {
case "text/html": {
const html = findBookmarkHtml(request.params.id, user)
if (html === null) {
throw new HttpError(404)
}
return new Response(html, {
status: 200,
headers: {
"Content-Type": "text/html",
},
})
}
default:
throw new HttpError(400, "UnsupportedAcceptHeader")
}
}
export { addBookmark, fetchBookmark, listUserBookmarks, deleteUserBookmark }