···
export function createRouter(ctx: AppContext): RequestListener {
51
-
const app = express()
51
+
const router = express()
54
-
app.use('/public', express.static(path.join(__dirname, 'pages', 'public')))
54
+
router.use('/public', express.static(path.join(__dirname, 'pages', 'public')))
'/oauth-client-metadata.json',
handler((req: Request, res: Response) => {
res.json(ctx.oauthClient.clientMetadata)
···
'/.well-known/jwks.json',
handler((req: Request, res: Response) => {
res.json(ctx.oauthClient.jwks)
···
// OAuth callback to complete session creation
handler(async (req: Request, res: Response) => {
const params = new URLSearchParams(req.originalUrl.split('?')[1])
···
session.did = oauth.session.did
101
-
if (oauth.state?.startsWith('status:')) {
102
-
const status = oauth.state.slice(7)
103
-
const agent = new Agent(oauth.session)
105
-
await updateStatus(agent, status)
107
-
const message = err instanceof Error ? err.message : 'Unknown error'
108
-
return res.redirect(`/?error=${encodeURIComponent(message)}`)
// Redirect to the homepage
···
handler((req: Request, res: Response) => {
125
-
const state = ifString(req.query.state)
126
-
res.type('html').send(page(login({ state })))
114
+
res.type('html').send(page(login({})))
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
const input = ifString(req.body.input)
136
-
const state = ifString(req.body.state)
142
-
.send(page(login({ error: 'invalid input' })))
127
+
res.type('html').send(page(login({ error: 'invalid input' })))
// Initiate the OAuth flow
const url = await ctx.oauthClient.authorize(input, {
scope: 'atproto transition:generic',
res.redirect(url.toString())
···
: "couldn't initiate login"
160
-
res.type('html').send(page(login({ state, error })))
145
+
res.type('html').send(page(login({ error })))
handler(async (req: Request, res: Response) => {
const service = env.PDS_URL ?? 'https://bsky.social'
const url = await ctx.oauthClient.authorize(service, {
scope: 'atproto transition:generic',
173
-
state: ifString(req.query.state),
res.redirect(url.toString())
···
handler(async (req: Request, res: Response) => {
const session = await getIronSession<Session>(req, res, {
···
handler(async (req: Request, res: Response) => {
const error = ifString(req.query.error)
···
// Serve the logged-out view
261
-
.send(page(home({ error, statuses, didHandleMap })))
243
+
res.type('html').send(page(home({ error, statuses, didHandleMap })))
// Fetch additional information about the logged-in user
···
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
294
-
const status = req.body?.status
// If the user is signed in, get an agent which communicates with their server
const agent = await getSessionAgent(req, res, ctx)
299
-
return void res.redirect(
300
-
`/login?state=status:${encodeURIComponent(status)}`,
280
+
res.redirect(`/login}`)
305
-
await updateStatus(agent, status)
306
-
return res.redirect('/')
285
+
const status = req.body?.status
286
+
if (typeof status !== 'string' || !STATUS_OPTIONS.includes(status)) {
287
+
throw new Error('Invalid status')
290
+
// Construct & validate their status record
291
+
const rkey = TID.nextStr()
293
+
$type: 'xyz.statusphere.status',
295
+
createdAt: new Date().toISOString(),
298
+
if (!Status.validateRecord(record).success) {
299
+
res.status(400).type('html').send('<h1>Error: Invalid status</h1>')
303
+
// Write the status record to the user's repository
306
+
const res = await agent.com.atproto.repo.putRecord({
307
+
repo: agent.assertDid,
308
+
collection: 'xyz.statusphere.status',
315
+
ctx.logger.error({ err }, 'failed to write record')
319
+
.send('<h1>Error: Failed to write record</h1>')
324
+
// Optimistically update our SQLite
325
+
// This isn't strictly necessary because the write event will be
326
+
// handled in #/firehose/ingestor.ts, but it ensures that future reads
327
+
// will be up-to-date after this method finishes.
329
+
.insertInto('status')
332
+
authorDid: agent.assertDid,
333
+
status: record.status,
334
+
createdAt: record.createdAt,
335
+
indexedAt: new Date().toISOString(),
341
+
'failed to update computed view; ignoring as it should be caught by the firehose',
const message = err instanceof Error ? err.message : 'Unknown error'
309
-
return res.redirect(`/?error=${encodeURIComponent(message)}`)
348
+
res.redirect(`/?error=${encodeURIComponent(message)}`)
316
-
async function updateStatus(agent: Agent, status: unknown) {
317
-
if (typeof status !== 'string' || !STATUS_OPTIONS.includes(status)) {
318
-
throw new Error('Invalid status')
321
-
// Construct & validate their status record
322
-
const rkey = TID.nextStr()
324
-
$type: 'xyz.statusphere.status',
326
-
createdAt: new Date().toISOString(),
329
-
if (!Status.validateRecord(record).success) {
330
-
throw new Error('Invalid status record')
333
-
// Write the status record to the user's repository
334
-
const res = await agent.com.atproto.repo
336
-
repo: agent.assertDid,
337
-
collection: 'xyz.statusphere.status',
343
-
ctx.logger.error({ err }, 'failed to write record')
344
-
throw new Error('Failed to write record', { cause: err })
348
-
// Optimistically update our SQLite
349
-
// This isn't strictly necessary because the write event will be
350
-
// handled in #/firehose/ingestor.ts, but it ensures that future reads
351
-
// will be up-to-date after this method finishes.
353
-
.insertInto('status')
356
-
authorDid: agent.assertDid,
357
-
status: record.status,
358
-
createdAt: record.createdAt,
359
-
indexedAt: new Date().toISOString(),
365
-
'failed to update computed view; ignoring as it should be caught by the firehose',