Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
wisp.place
1import { Elysia } from 'elysia'
2import { NodeOAuthClient } from '@atproto/oauth-client-node'
3import { getSitesByDid, getDomainByDid } from '../lib/db'
4import { syncSitesFromPDS } from '../lib/sync-sites'
5
6export const authRoutes = (client: NodeOAuthClient) => new Elysia()
7 .post('/api/auth/signin', async (c) => {
8 try {
9 const { handle } = await c.request.json()
10 const state = crypto.randomUUID()
11 const url = await client.authorize(handle, { state })
12 return { url: url.toString() }
13 } catch (err) {
14 console.error('Signin error', err)
15 return { error: 'Authentication failed' }
16 }
17 })
18 .get('/api/auth/callback', async (c) => {
19 const params = new URLSearchParams(c.query)
20 const { session } = await client.callback(params)
21 if (!session) return { error: 'Authentication failed' }
22
23 const cookieSession = c.cookie
24 cookieSession.did.value = session.did
25
26 // Sync sites from PDS to database cache
27 console.log('[Auth] Syncing sites from PDS for', session.did)
28 try {
29 const syncResult = await syncSitesFromPDS(session.did, session)
30 console.log(`[Auth] Sync complete: ${syncResult.synced} sites synced`)
31 if (syncResult.errors.length > 0) {
32 console.warn('[Auth] Sync errors:', syncResult.errors)
33 }
34 } catch (err) {
35 console.error('[Auth] Failed to sync sites:', err)
36 // Don't fail auth if sync fails, just log it
37 }
38
39 // Check if user has any sites or domain
40 const sites = await getSitesByDid(session.did)
41 const domain = await getDomainByDid(session.did)
42
43 // If no sites and no domain, redirect to onboarding
44 if (sites.length === 0 && !domain) {
45 return c.redirect('/onboarding')
46 }
47
48 return c.redirect('/editor')
49 })