A fullstack app for indexing standard.site documents
1import { Hono } from "hono";
2import type {
3 Bindings,
4 ResolvedDocumentRow,
5 Document,
6 Publication,
7 BskyPostRef,
8} from "../types";
9
10const feed = new Hono<{ Bindings: Bindings }>();
11
12/**
13 * Transforms a database row into a Document object for the API response.
14 */
15function rowToDocument(row: ResolvedDocumentRow): Document {
16 // Build publication object if we have publication data
17 let publication: Publication | undefined;
18 if (row.pub_url && row.pub_name) {
19 publication = {
20 url: row.pub_url,
21 name: row.pub_name,
22 description: row.pub_description || undefined,
23 iconCid: row.pub_icon_cid || undefined,
24 iconUrl: row.pub_icon_url || undefined,
25 };
26 }
27
28 // Parse bskyPostRef if present
29 let bskyPostRef: BskyPostRef | undefined;
30 if (row.bsky_post_ref) {
31 try {
32 bskyPostRef = JSON.parse(row.bsky_post_ref);
33 } catch {
34 // Ignore parse errors
35 }
36 }
37
38 // Parse tags if present
39 let tags: string[] | undefined;
40 if (row.tags) {
41 try {
42 tags = JSON.parse(row.tags);
43 } catch {
44 // Ignore parse errors
45 }
46 }
47
48 return {
49 uri: row.uri,
50 did: row.did,
51 rkey: row.rkey,
52 title: row.title || "Untitled",
53 description: row.description || undefined,
54 path: row.path || undefined,
55 site: row.site || undefined,
56 textContent: row.text_content || undefined,
57 coverImageCid: row.cover_image_cid || undefined,
58 coverImageUrl: row.cover_image_url || undefined,
59 bskyPostRef,
60 tags,
61 publishedAt: row.published_at || undefined,
62 updatedAt: row.updated_at || undefined,
63 publication,
64 viewUrl: row.view_url || undefined,
65 pdsEndpoint: row.pds_endpoint || undefined,
66 };
67}
68
69// Get feed of documents with resolved URLs (server-side resolution)
70feed.get("/", async (c) => {
71 try {
72 const db = c.env.DB;
73 const limit = Number(c.req.query("limit")) || 50;
74 const offset = Number(c.req.query("offset")) || 0;
75
76 const { results } = await db
77 .prepare(
78 `SELECT uri, did, rkey, title, description, path, site, text_content,
79 cover_image_cid, cover_image_url, bsky_post_ref, tags,
80 published_at, updated_at, pub_url, pub_name, pub_description,
81 pub_icon_cid, pub_icon_url, view_url, pds_endpoint,
82 resolved_at, stale_at, verified
83 FROM resolved_documents
84 WHERE verified = 1
85 ORDER BY published_at DESC
86 LIMIT ? OFFSET ?`,
87 )
88 .bind(limit, offset)
89 .all<ResolvedDocumentRow>();
90
91 const documents = (results || []).map(rowToDocument);
92
93 return c.json({
94 count: documents.length,
95 limit,
96 offset,
97 documents,
98 });
99 } catch (error) {
100 return c.json(
101 { error: "Failed to fetch feed", details: String(error) },
102 500,
103 );
104 }
105});
106
107export default feed;