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}