import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { storeTrelloTokenContract } from '@/lib/api/contracts/oauth-connections' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { env } from '@/lib/core/config/env' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { processCredentialDraft } from '@/lib/credentials/draft-processor' import { getCanonicalScopesForProvider } from '@/lib/oauth/utils' import { safeAccountInsert } from '@/app/api/auth/oauth/utils' const logger = createLogger('TrelloStore') export const dynamic = 'force-dynamic' const TRELLO_STATE_COOKIE = 'trello_oauth_state' const TRELLO_STATE_COOKIE_PATH = '/api/auth/trello' function clearStateCookie(response: NextResponse) { response.cookies.delete({ name: TRELLO_STATE_COOKIE, path: TRELLO_STATE_COOKIE_PATH }) return response } export const POST = withRouteHandler(async (request: NextRequest) => { try { const session = await getSession() if (!session?.user?.id) { logger.warn('Unauthorized attempt to store Trello token') return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }) } const parsed = await parseRequest(storeTrelloTokenContract, request, {}) if (!parsed.success) return parsed.response const { token, state } = parsed.data.body const cookieState = request.cookies.get(TRELLO_STATE_COOKIE)?.value if (!cookieState || cookieState !== state) { logger.warn('Trello store rejected: state mismatch', { hasCookieState: Boolean(cookieState), userId: session.user.id, }) return clearStateCookie( NextResponse.json( { success: false, error: 'Invalid or expired authorization state' }, { status: 400 } ) ) } const apiKey = env.TRELLO_API_KEY if (!apiKey) { logger.error('TRELLO_API_KEY not configured') return NextResponse.json({ success: false, error: 'Trello not configured' }, { status: 500 }) } const scope = getCanonicalScopesForProvider('trello').join(',') const validationUrl = `https://api.trello.com/1/members/me?key=${apiKey}&token=${token}&fields=id,username,fullName` const userResponse = await fetch(validationUrl, { headers: { Accept: 'application/json' }, }) if (!userResponse.ok) { const errorText = await userResponse.text() logger.error('Invalid Trello token', { status: userResponse.status, error: errorText, }) return NextResponse.json( { success: false, error: `Invalid Trello token: ${errorText}` }, { status: 400 } ) } const trelloUser = (await userResponse.json().catch(() => null)) as { id?: string } | null if (typeof trelloUser?.id !== 'string' || trelloUser.id.trim().length === 0) { logger.error('Trello validation response did not include a valid member id', { response: trelloUser, }) return NextResponse.json( { success: false, error: 'Invalid Trello member response' }, { status: 502 } ) } const existing = await db.query.account.findFirst({ where: and( eq(account.userId, session.user.id), eq(account.providerId, 'trello'), eq(account.accountId, trelloUser.id) ), }) const now = new Date() if (existing) { await db .update(account) .set({ accessToken: token, accountId: trelloUser.id, scope, updatedAt: now, }) .where(eq(account.id, existing.id)) } else { await safeAccountInsert( { id: `trello_${session.user.id}_${Date.now()}`, userId: session.user.id, providerId: 'trello', accountId: trelloUser.id, accessToken: token, scope, createdAt: now, updatedAt: now, }, { provider: 'Trello', identifier: trelloUser.id } ) } const persisted = existing ?? (await db.query.account.findFirst({ where: and( eq(account.userId, session.user.id), eq(account.providerId, 'trello'), eq(account.accountId, trelloUser.id) ), })) if (persisted) { try { await processCredentialDraft({ userId: session.user.id, providerId: 'trello', accountId: persisted.id, }) } catch (error) { logger.error('Failed to process credential draft for Trello', { error }) } } return clearStateCookie(NextResponse.json({ success: true })) } catch (error) { logger.error('Error storing Trello token:', error) return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }) } })