forked from
nekomimi.pet/wisp.place-monorepo
Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.
1import { Agent } from '@atproto/api'
2import type { OAuthSession } from '@atproto/oauth-client-node'
3import { upsertSite } from './db'
4
5/**
6 * Sync sites from user's PDS into the database cache
7 * - Fetches all place.wisp.fs records from AT Protocol repo
8 * - Validates record structure
9 * - Backfills into sites table
10 */
11export async function syncSitesFromPDS(
12 did: string,
13 session: OAuthSession
14): Promise<{ synced: number; errors: string[] }> {
15 console.log(`[Sync] Starting site sync for ${did}`)
16
17 const agent = new Agent((url, init) => session.fetchHandler(url, init))
18 const errors: string[] = []
19 let synced = 0
20
21 try {
22 // List all records in the place.wisp.fs collection
23 console.log('[Sync] Fetching place.wisp.fs records from PDS')
24 const records = await agent.com.atproto.repo.listRecords({
25 repo: did,
26 collection: 'place.wisp.fs',
27 limit: 100 // Adjust if users might have more sites
28 })
29
30 console.log(`[Sync] Found ${records.data.records.length} records`)
31
32 // Process each record
33 for (const record of records.data.records) {
34 try {
35 const { uri, value } = record
36
37 // Extract rkey from URI (at://did/collection/rkey)
38 const rkey = uri.split('/').pop()
39 if (!rkey) {
40 errors.push(`Invalid URI format: ${uri}`)
41 continue
42 }
43
44 // Validate record structure
45 if (!value || typeof value !== 'object') {
46 errors.push(`Invalid record value for ${rkey}`)
47 continue
48 }
49
50 const siteValue = value as any
51
52 // Check for required fields
53 if (siteValue.$type !== 'place.wisp.fs') {
54 errors.push(
55 `Invalid $type for ${rkey}: ${siteValue.$type}`
56 )
57 continue
58 }
59
60 if (!siteValue.site || typeof siteValue.site !== 'string') {
61 errors.push(`Missing or invalid site name for ${rkey}`)
62 continue
63 }
64
65 // Upsert into database
66 const displayName = siteValue.site
67 await upsertSite(did, rkey, displayName)
68
69 console.log(
70 `[Sync] ✓ Synced site: ${displayName} (${rkey})`
71 )
72 synced++
73 } catch (err) {
74 const errorMsg = `Error processing record: ${err instanceof Error ? err.message : 'Unknown error'}`
75 console.error(`[Sync] ${errorMsg}`)
76 errors.push(errorMsg)
77 }
78 }
79
80 console.log(
81 `[Sync] Complete: ${synced} synced, ${errors.length} errors`
82 )
83 return { synced, errors }
84 } catch (err) {
85 const errorMsg = `Failed to fetch records from PDS: ${err instanceof Error ? err.message : 'Unknown error'}`
86 console.error(`[Sync] ${errorMsg}`)
87 errors.push(errorMsg)
88 return { synced, errors }
89 }
90}