Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1import { Elysia, t } from 'elysia' 2import { requireAuth } from '../lib/wisp-auth' 3import { NodeOAuthClient } from '@atproto/oauth-client-node' 4import { Agent } from '@atproto/api' 5import { getSitesByDid, getDomainByDid, getCustomDomainsByDid, getWispDomainInfo, getDomainsBySite, getAllWispDomains } from '../lib/db' 6import { syncSitesFromPDS } from '../lib/sync-sites' 7import { logger } from '../lib/logger' 8 9export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) => 10 new Elysia({ 11 prefix: '/api/user', 12 cookie: { 13 secrets: cookieSecret, 14 sign: ['did'] 15 } 16 }) 17 .derive(async ({ cookie }) => { 18 const auth = await requireAuth(client, cookie) 19 return { auth } 20 }) 21 .get('/status', async ({ auth }) => { 22 try { 23 // Check if user has any sites 24 const sites = await getSitesByDid(auth.did) 25 26 // Check if user has claimed a domain 27 const domain = await getDomainByDid(auth.did) 28 29 return { 30 did: auth.did, 31 hasSites: sites.length > 0, 32 hasDomain: !!domain, 33 domain: domain || null, 34 sitesCount: sites.length 35 } 36 } catch (err) { 37 logger.error('[User] Status error', err) 38 throw new Error('Failed to get user status') 39 } 40 }) 41 .get('/info', async ({ auth }) => { 42 try { 43 // Get user's handle from AT Protocol 44 const agent = new Agent((url, init) => auth.session.fetchHandler(url, init)) 45 46 let handle = 'unknown' 47 try { 48 const profile = await agent.getProfile({ actor: auth.did }) 49 handle = profile.data.handle 50 } catch (err) { 51 logger.error('[User] Failed to fetch profile', err) 52 } 53 54 return { 55 did: auth.did, 56 handle 57 } 58 } catch (err) { 59 logger.error('[User] Info error', err) 60 throw new Error('Failed to get user info') 61 } 62 }) 63 .get('/sites', async ({ auth }) => { 64 try { 65 const sites = await getSitesByDid(auth.did) 66 return { sites } 67 } catch (err) { 68 logger.error('[User] Sites error', err) 69 throw new Error('Failed to get sites') 70 } 71 }) 72 .get('/domains', async ({ auth }) => { 73 try { 74 // Get all wisp.place subdomains with mappings (up to 3) 75 const wispDomains = await getAllWispDomains(auth.did) 76 77 // Get custom domains 78 const customDomains = await getCustomDomainsByDid(auth.did) 79 80 return { 81 wispDomains: wispDomains.map(d => ({ 82 domain: d.domain, 83 rkey: d.rkey || null 84 })), 85 customDomains 86 } 87 } catch (err) { 88 logger.error('[User] Domains error', err) 89 throw new Error('Failed to get domains') 90 } 91 }) 92 .post('/sync', async ({ auth }) => { 93 try { 94 logger.debug('[User] Manual sync requested for', auth.did) 95 const result = await syncSitesFromPDS(auth.did, auth.session) 96 97 return { 98 success: true, 99 synced: result.synced, 100 errors: result.errors 101 } 102 } catch (err) { 103 logger.error('[User] Sync error', err) 104 throw new Error('Failed to sync sites') 105 } 106 }) 107 .get('/site/:rkey/domains', async ({ auth, params }) => { 108 try { 109 const { rkey } = params 110 const domains = await getDomainsBySite(auth.did, rkey) 111 112 return { 113 rkey, 114 domains 115 } 116 } catch (err) { 117 logger.error('[User] Site domains error', err) 118 throw new Error('Failed to get domains for site') 119 } 120 })