import { NextRequest } from 'next/server' import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3' import { DynamoDBClient } from '@aws-sdk/client-dynamodb' import { DynamoDBDocumentClient, GetCommand, QueryCommand } from '@aws-sdk/lib-dynamodb' import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' const s3 = new S3Client({}) const dbClient = new DynamoDBClient({}) const doc = DynamoDBDocumentClient.from(dbClient) const ses = new SESClient({}) function getIp(req: NextRequest) { const xf = req.headers.get('x-forwarded-for') || '' const ip = xf.split(',')[0].trim() || req.headers.get('x-real-ip') || '' return String(ip) } function getUa(req: NextRequest) { return req.headers.get('user-agent') || '' } function getLocationHeaders(req: NextRequest) { const country = req.headers.get('x-vercel-ip-country') || '' const city = req.headers.get('x-vercel-ip-city') || '' return { country, city } } function escapeHtml(str: string) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') } export async function createSupportPresignRoute(req: NextRequest, userId: string) { try { const { filename, mime } = await req.json() const bucket = process.env.SUPPORT_SCREENSHOTS_BUCKET as string if (!bucket) return Response.json({ error: 'Bucket not configured' }, { status: 500 }) const safeName = String(filename || 'upload').replace(/[^a-zA-Z0-9._-]/g, '_') const ext = safeName.includes('.') ? safeName.split('.').pop() : 'png' const key = `${userId}/${Date.now()}-${Math.random().toString(36).slice(2)}.${ext}` const command = new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: String(mime || 'image/png') }) const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 60 * 5 }) return Response.json({ uploadUrl, key }) } catch (e) { console.error('support presign error', e) return Response.json({ error: 'Failed to create upload URL' }, { status: 500 }) } } export async function createSupportTicketRoute(req: NextRequest, userId: string) { try { const body = await req.json().catch(() => ({})) const subject: string = String(body?.subject || '') const description: string = String(body?.description || '') const screenshotKey: string | undefined = body?.screenshotKey if (!subject || !description) return Response.json({ error: 'Missing fields' }, { status: 400 }) const userTable = process.env.USER_TABLE_NAME as string const badgeTable = process.env.BADGE_TABLE_NAME as string const supportBucket = process.env.SUPPORT_SCREENSHOTS_BUCKET as string const sesFrom = process.env.SES_FROM_EMAIL as string if (!userTable || !badgeTable || !sesFrom) return Response.json({ error: 'Server not configured' }, { status: 500 }) // Get user to find stripeCustomerId + totals const userResp = await doc.send(new GetCommand({ TableName: userTable, Key: { userId } })) const userItem = (userResp as any).Item || {} const stripeCustomerId: string | undefined = userItem.stripeCustomerId // Stripe total spent let totalSpentUsd = 0 try { const stripeKey = process.env.STRIPE_SECRET_KEY if (stripeKey && stripeCustomerId) { const Stripe = (await import('stripe')).default const stripe = new Stripe(stripeKey) let hasMore = true let startingAfter: string | undefined while (hasMore) { const inv = await stripe.invoices.list({ customer: stripeCustomerId, status: 'paid', limit: 100, starting_after: startingAfter }) for (const i of inv.data) totalSpentUsd += (i.total || 0) hasMore = inv.has_more startingAfter = inv.data[inv.data.length - 1]?.id } totalSpentUsd = Math.round(totalSpentUsd) / 100 } } catch {} // Total reports run by user let totalReports = 0 try { const countResp = await doc.send(new QueryCommand({ TableName: badgeTable, IndexName: 'userId-index', KeyConditionExpression: 'userId = :u', ExpressionAttributeValues: { ':u': userId }, Select: 'COUNT', })) totalReports = (countResp as any).Count || 0 } catch {} const ip = getIp(req) const ua = getUa(req) const { country, city } = getLocationHeaders(req) let screenshotUrl: string | undefined if (screenshotKey && supportBucket) { const getCmd = new GetObjectCommand({ Bucket: supportBucket, Key: screenshotKey }) screenshotUrl = await getSignedUrl(s3, getCmd, { expiresIn: 60 * 30 }) } const product = 'KYD Self-check' const name = userItem?.fullName || userItem?.name || userItem?.email || '' const email = userItem?.email || '' const html = `

New Support Request

User

Details

Meta

${screenshotUrl ? `

View Screenshot

` : ''}
` const send = new SendEmailCommand({ Source: sesFrom, Destination: { ToAddresses: ['team@knowyourdeveloper.ai'] }, Message: { Subject: { Data: `Support: ${subject}` }, Body: { Html: { Data: html } } }, }) await ses.send(send) return Response.json({ ok: true }) } catch (e) { console.error('support submit error', e) return Response.json({ error: 'Failed to send support request' }, { status: 500 }) } }