From 5269de150aae22e74789b1c582b7406c5cd884ac Mon Sep 17 00:00:00 2001 From: Steve Korshakov Date: Tue, 26 Aug 2025 23:18:05 -0700 Subject: [PATCH] feat: add GitHub account disconnect endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DELETE /v1/connect/github endpoint to disconnect GitHub accounts by: - Deleting GitHub user token from database - Removing account link (githubUserId = null) - Broadcasting disconnect event to connected clients - Using database transaction for atomicity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- sources/app/api.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/sources/app/api.ts b/sources/app/api.ts index 0f6930b..a3b8683 100644 --- a/sources/app/api.ts +++ b/sources/app/api.ts @@ -598,6 +598,74 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> } }); + // GitHub disconnect endpoint + typed.delete('/v1/connect/github', { + preHandler: app.authenticate, + schema: { + response: { + 200: z.object({ + success: z.literal(true) + }), + 404: z.object({ + error: z.string() + }), + 500: z.object({ + error: z.string() + }) + } + } + }, async (request, reply) => { + const userId = request.userId; + + try { + // Get current user's GitHub connection + const user = await db.account.findUnique({ + where: { id: userId }, + select: { githubUserId: true } + }); + + if (!user || !user.githubUserId) { + return reply.code(404).send({ error: 'GitHub account not connected' }); + } + + const githubUserId = user.githubUserId; + log({ module: 'github-disconnect' }, `Disconnecting GitHub account for user ${userId}: ${githubUserId}`); + + // Remove GitHub connection from account and delete GitHub user record + await db.$transaction(async (tx) => { + // Remove link from account + await tx.account.update({ + where: { id: userId }, + data: { githubUserId: null } + }); + + // Delete GitHub user record (this also deletes the token) + await tx.githubUser.delete({ + where: { id: githubUserId } + }); + }); + + // Send account update to all user connections + const updSeq = await allocateUserSeq(userId); + const updatePayload = buildUpdateAccountUpdate(userId, { + github: null + }, updSeq, randomKeyNaked(12)); + eventRouter.emitUpdate({ + userId, + payload: updatePayload, + recipientFilter: { type: 'all-user-authenticated-connections' } + }); + + log({ module: 'github-disconnect' }, `GitHub account disconnected successfully for user ${userId}`); + + return reply.send({ success: true }); + + } catch (error) { + log({ module: 'github-disconnect', level: 'error' }, `Error disconnecting GitHub account: ${error}`); + return reply.code(500).send({ error: 'Failed to disconnect GitHub account' }); + } + }); + // Account auth request typed.post('/v1/auth/account/request', { schema: {