Scratch space for learning atproto app development
1import { OAuthResolverError } from '@atproto/oauth-client-node' 2import { isValidHandle } from '@atproto/syntax' 3import express from 'express' 4import { createSession, destroySession, getSession } from '#/auth/session' 5import type { AppContext } from '#/config' 6import { home } from '#/pages/home' 7import { login } from '#/pages/login' 8import { page } from '#/view' 9import { handler } from './util' 10 11export const createRouter = (ctx: AppContext) => { 12 const router = express.Router() 13 14 router.get( 15 '/client-metadata.json', 16 handler((_req, res) => { 17 return res.json(ctx.oauthClient.clientMetadata) 18 }), 19 ) 20 21 router.get( 22 '/oauth/callback', 23 handler(async (req, res) => { 24 const params = new URLSearchParams(req.originalUrl.split('?')[1]) 25 try { 26 const { agent } = await ctx.oauthClient.callback(params) 27 await createSession(req, res, agent.accountDid) 28 } catch (err) { 29 ctx.logger.error({ err }, 'oauth callback failed') 30 return res.redirect('/?error') 31 } 32 return res.redirect('/') 33 }), 34 ) 35 36 router.get( 37 '/login', 38 handler(async (_req, res) => { 39 return res.type('html').send(page(login({}))) 40 }), 41 ) 42 43 router.post( 44 '/login', 45 handler(async (req, res) => { 46 const handle = req.body?.handle 47 if (typeof handle !== 'string' || !isValidHandle(handle)) { 48 return res.type('html').send(page(login({ error: 'invalid handle' }))) 49 } 50 try { 51 const url = await ctx.oauthClient.authorize(handle) 52 return res.redirect(url.toString()) 53 } catch (err) { 54 ctx.logger.error({ err }, 'oauth authorize failed') 55 return res.type('html').send( 56 page( 57 login({ 58 error: err instanceof OAuthResolverError ? err.message : "couldn't initiate login", 59 }), 60 ), 61 ) 62 } 63 }), 64 ) 65 66 router.post( 67 '/logout', 68 handler(async (req, res) => { 69 await destroySession(req, res) 70 return res.redirect('/') 71 }), 72 ) 73 74 router.get( 75 '/', 76 handler(async (req, res) => { 77 const session = await getSession(req, res) 78 const agent = 79 session && 80 (await ctx.oauthClient.restore(session.did).catch(async (err) => { 81 ctx.logger.warn({ err }, 'oauth restore failed') 82 await destroySession(req, res) 83 return null 84 })) 85 const posts = await ctx.db.selectFrom('post').selectAll().orderBy('indexedAt', 'desc').limit(10).execute() 86 if (!agent) { 87 return res.type('html').send(page(home({ posts }))) 88 } 89 const { data: profile } = await agent.getProfile({ actor: session.did }) 90 return res.type('html').send(page(home({ posts, profile }))) 91 }), 92 ) 93 94 return router 95}