the statusphere demo reworked into a vite/react app in a monorepo

Merge pull request #5 from bluesky-social/paul/backend-simplifications

Backend simplifications

authored by

Paul Frazee and committed by
GitHub
d1b4d6b1 1668c812

+42 -94
+32 -4
src/auth/session.ts
··· 4 4 import type { IncomingMessage, ServerResponse } from 'node:http' 5 5 import { getIronSession } from 'iron-session' 6 6 import { env } from '#/env' 7 + import { AppContext } from '#/config' 7 8 8 9 export type Session = { did: string } 9 10 10 - export async function createSession(req: IncomingMessage, res: ServerResponse<IncomingMessage>, did: string) { 11 + export async function createSession( 12 + req: IncomingMessage, 13 + res: ServerResponse<IncomingMessage>, 14 + did: string 15 + ) { 11 16 const session = await getSessionRaw(req, res) 12 17 assert(!session.did, 'session already exists') 13 18 session.did = did ··· 15 20 return { did: session.did } 16 21 } 17 22 18 - export async function destroySession(req: IncomingMessage, res: ServerResponse<IncomingMessage>) { 23 + export async function destroySession( 24 + req: IncomingMessage, 25 + res: ServerResponse<IncomingMessage> 26 + ) { 19 27 const session = await getSessionRaw(req, res) 20 28 await session.destroy() 21 29 return null 22 30 } 23 31 24 - export async function getSession(req: IncomingMessage, res: ServerResponse<IncomingMessage>) { 32 + export async function getSession( 33 + req: IncomingMessage, 34 + res: ServerResponse<IncomingMessage> 35 + ) { 25 36 const session = await getSessionRaw(req, res) 26 37 if (!session.did) return null 27 38 return { did: session.did } 28 39 } 29 40 30 - async function getSessionRaw(req: IncomingMessage, res: ServerResponse<IncomingMessage>) { 41 + export async function getSessionAgent( 42 + req: IncomingMessage, 43 + res: ServerResponse<IncomingMessage>, 44 + ctx: AppContext 45 + ) { 46 + const session = await getSessionRaw(req, res) 47 + if (!session.did) return null 48 + return await ctx.oauthClient.restore(session.did).catch(async (err) => { 49 + ctx.logger.warn({ err }, 'oauth restore failed') 50 + await destroySession(req, res) 51 + return null 52 + }) 53 + } 54 + 55 + async function getSessionRaw( 56 + req: IncomingMessage, 57 + res: ServerResponse<IncomingMessage> 58 + ) { 31 59 return await getIronSession<Session>(req, res, { 32 60 cookieName: 'sid', 33 61 password: env.COOKIE_SECRET,
-7
src/db/migrations.ts
··· 11 11 migrations['001'] = { 12 12 async up(db: Kysely<unknown>) { 13 13 await db.schema 14 - .createTable('did_cache') 15 - .addColumn('did', 'varchar', (col) => col.primaryKey()) 16 - .addColumn('doc', 'varchar', (col) => col.notNull()) 17 - .addColumn('updatedAt', 'varchar', (col) => col.notNull()) 18 - .execute() 19 - await db.schema 20 14 .createTable('status') 21 15 .addColumn('authorDid', 'varchar', (col) => col.primaryKey()) 22 16 .addColumn('status', 'varchar', (col) => col.notNull()) ··· 38 32 await db.schema.dropTable('auth_state').execute() 39 33 await db.schema.dropTable('auth_session').execute() 40 34 await db.schema.dropTable('status').execute() 41 - await db.schema.dropTable('did_cache').execute() 42 35 }, 43 36 }
-7
src/db/schema.ts
··· 1 1 export type DatabaseSchema = { 2 - did_cache: DidCache 3 2 status: Status 4 3 auth_session: AuthSession 5 4 auth_state: AuthState 6 - } 7 - 8 - export type DidCache = { 9 - did: string 10 - doc: string 11 - updatedAt: string 12 5 } 13 6 14 7 export type Status = {
+3 -57
src/ident/resolver.ts
··· 1 - import { IdResolver, DidDocument, CacheResult } from '@atproto/identity' 2 - import type { Database } from '#/db' 1 + import { IdResolver, MemoryCache } from '@atproto/identity' 3 2 4 3 const HOUR = 60e3 * 60 5 4 const DAY = HOUR * 24 6 5 7 - export function createResolver(db: Database) { 6 + export function createResolver() { 8 7 const resolver = new IdResolver({ 9 - didCache: { 10 - async cacheDid(did: string, doc: DidDocument): Promise<void> { 11 - await db 12 - .insertInto('did_cache') 13 - .values({ 14 - did, 15 - doc: JSON.stringify(doc), 16 - updatedAt: new Date().toISOString(), 17 - }) 18 - .onConflict((oc) => 19 - oc.column('did').doUpdateSet({ 20 - doc: JSON.stringify(doc), 21 - updatedAt: new Date().toISOString(), 22 - }) 23 - ) 24 - .execute() 25 - }, 26 - 27 - async checkCache(did: string): Promise<CacheResult | null> { 28 - const row = await db 29 - .selectFrom('did_cache') 30 - .selectAll() 31 - .where('did', '=', did) 32 - .executeTakeFirst() 33 - if (!row) return null 34 - const now = Date.now() 35 - const updatedAt = +new Date(row.updatedAt) 36 - return { 37 - did, 38 - doc: JSON.parse(row.doc), 39 - updatedAt, 40 - stale: now > updatedAt + HOUR, 41 - expired: now > updatedAt + DAY, 42 - } 43 - }, 44 - 45 - async refreshCache( 46 - did: string, 47 - getDoc: () => Promise<DidDocument | null> 48 - ): Promise<void> { 49 - const doc = await getDoc() 50 - if (doc) { 51 - await this.cacheDid(did, doc) 52 - } 53 - }, 54 - 55 - async clearEntry(did: string): Promise<void> { 56 - await db.deleteFrom('did_cache').where('did', '=', did).execute() 57 - }, 58 - 59 - async clear(): Promise<void> { 60 - await db.deleteFrom('did_cache').execute() 61 - }, 62 - }, 8 + didCache: new MemoryCache(HOUR, DAY), 63 9 }) 64 10 65 11 return {
+6 -18
src/routes/index.ts
··· 2 2 import { OAuthResolverError } from '@atproto/oauth-client-node' 3 3 import { isValidHandle } from '@atproto/syntax' 4 4 import express from 'express' 5 - import { createSession, destroySession, getSession } from '#/auth/session' 5 + import { createSession, destroySession, getSessionAgent } from '#/auth/session' 6 6 import type { AppContext } from '#/config' 7 7 import { home } from '#/pages/home' 8 8 import { login } from '#/pages/login' ··· 81 81 router.get( 82 82 '/', 83 83 handler(async (req, res) => { 84 - const session = await getSession(req, res) 85 - const agent = 86 - session && 87 - (await ctx.oauthClient.restore(session.did).catch(async (err) => { 88 - ctx.logger.warn({ err }, 'oauth restore failed') 89 - await destroySession(req, res) 90 - return null 91 - })) 84 + const agent = await getSessionAgent(req, res, ctx) 92 85 const statuses = await ctx.db 93 86 .selectFrom('status') 94 87 .selectAll() ··· 108 101 if (!agent) { 109 102 return res.type('html').send(page(home({ statuses, didHandleMap }))) 110 103 } 111 - const { data: profile } = await agent.getProfile({ actor: session.did }) 104 + const { data: profile } = await agent.getProfile({ 105 + actor: agent.accountDid, 106 + }) 112 107 return res 113 108 .type('html') 114 109 .send(page(home({ statuses, didHandleMap, profile, myStatus }))) ··· 118 113 router.post( 119 114 '/status', 120 115 handler(async (req, res) => { 121 - const session = await getSession(req, res) 122 - const agent = 123 - session && 124 - (await ctx.oauthClient.restore(session.did).catch(async (err) => { 125 - ctx.logger.warn({ err }, 'oauth restore failed') 126 - await destroySession(req, res) 127 - return null 128 - })) 116 + const agent = await getSessionAgent(req, res, ctx) 129 117 if (!agent) { 130 118 return res.status(401).json({ error: 'Session required' }) 131 119 }
+1 -1
src/server.ts
··· 30 30 await migrateToLatest(db) 31 31 const ingester = new Ingester(db) 32 32 const oauthClient = await createClient(db) 33 - const resolver = await createResolver(db) 33 + const resolver = createResolver() 34 34 ingester.start() 35 35 const ctx = { 36 36 db,