mirror your GitHub repos to tangled.org automatically
1

Configure Feed

Select the types of activity you want to include in your feed.

1import { eq } from 'drizzle-orm' 2import { userIdentity } from '~~/server/db/schema' 3import { enqueue } from '~~/server/utils/queue' 4import { writeSession } from '~~/server/utils/server-session' 5import { generateAndPublishKey } from '~~/server/utils/tangled-pubkey' 6 7export default defineEventHandler(async event => { 8 const url = getRequestURL(event) 9 const params = url.searchParams 10 11 const client = await useOAuthClient() 12 const { session, state } = await client.callback(params) 13 14 const db = useDb() 15 16 // Two flows land here: 17 // 18 // - **First-time connect** (`state` set to the GitHub installation id). 19 // Bind the resulting `user_identity` row, publish an SSH key, kick off 20 // repo backfill. This is the "install GitHub App → connect tangled" 21 // ordering. 22 // - **Returning sign-in** (`state` absent). The user already has a 23 // `user_identity` row; we look it up by DID and write a fresh session 24 // so they can use the dashboard on a new device / browser. 25 // 26 // If `state` is present but invalid, or `state` is absent but no 27 // `user_identity` exists for the DID, we send the user to install the 28 // GitHub App. Trying to land them on `/dashboard` with no installation 29 // would just show a useless page. 30 31 let installationId: number | undefined 32 33 if (state) { 34 const parsed = Number(state) 35 if (!Number.isFinite(parsed)) { 36 throw createError({ statusCode: 400, statusMessage: 'invalid state (non-numeric installation id)' }) 37 } 38 installationId = parsed 39 40 await db.insert(userIdentity).values({ 41 did: session.did, 42 handle: null, 43 installationId, 44 updatedAt: new Date(), 45 }).onConflictDoUpdate({ 46 target: userIdentity.did, 47 set: { installationId, updatedAt: new Date() }, 48 }) 49 50 // Generate and publish the SSH key inline: it's one ed25519 keygen + one 51 // PDS write, well under the function timeout, and lets us land users on 52 // the dashboard already enrolled. Rotation is a separate dashboard 53 // action that goes via the queue. 54 await generateAndPublishKey({ oauthSession: session, installationId }) 55 56 // Backfill: enqueue a single job that walks the installation's repo list 57 // and fans out per-repo enrolment. Doing this in the worker (rather than 58 // inline here) keeps the OAuth callback fast regardless of repo count. 59 await enqueue('tangled.backfill-installation', { installationId, page: 1 }) 60 } 61 else { 62 // Returning sign-in. Look up the installation we previously bound. 63 const rows = await db.select({ installationId: userIdentity.installationId }) 64 .from(userIdentity) 65 .where(eq(userIdentity.did, session.did)) 66 .limit(1) 67 68 if (rows.length === 0 || rows[0]!.installationId === null) { 69 // Authenticated tangled identity, but no GitHub install bound. Send 70 // them to install the App. We deliberately do NOT write a session 71 // yet — the dashboard needs an installation to be useful. 72 const appInstallUrl = process.env.NUXT_GITHUB_APP_INSTALL_URL 73 ?? 'https://github.com/apps/synchub-to/installations/new' 74 await sendRedirect(event, appInstallUrl, 302) 75 return 76 } 77 installationId = rows[0]!.installationId 78 } 79 80 // Sealed cookie session for the dashboard. Handle resolution is deferred. 81 await writeSession(event, { did: session.did, installationId }) 82 83 await sendRedirect(event, '/dashboard', 302) 84})