import { Router, Request, Response } from 'express' import { sessionAuth } from '../middleware/auth.js' import { generateBillingPDF, verifyHmacSignature } from '../utils/pdf.js' import db from '../db.js' const router = Router() // POST /api/billing/export/pdf router.post('/export/pdf', sessionAuth, async (req: Request, res: Response) => { const session = req.session! const { startDate, endDate } = req.body const userInfo = JSON.parse(session.user_info) const site = db.prepare('SELECT name, url FROM sites WHERE id = ?').get(session.site_id) as any try { const startTs = Math.floor(new Date(startDate).getTime() / 1000) const endTs = Math.floor(new Date(endDate).getTime() / 1000) let allLogs: any[] = [] let page = 1 const pageSize = 100 let hasMore = true while (hasMore) { const logRes = await fetch( `${session.site_url}/api/log/self?start_timestamp=${startTs}&end_timestamp=${endTs}&type=2&p=${page}&page_size=${pageSize}`, { headers: { 'Authorization': session.access_token } } ) const logData = await logRes.json() as any if (!logData.success || !logData.data?.items?.length) { hasMore = false } else { allLogs = allLogs.concat(logData.data.items) hasMore = logData.data.items.length === pageSize page++ } if (allLogs.length > 10000) hasMore = false } const modelMap = new Map() let totalQuota = 0 for (const log of allLogs) { totalQuota += log.quota || 0 const model = log.model_name || 'unknown' const existing = modelMap.get(model) || { quota: 0, count: 0 } existing.quota += log.quota || 0 existing.count += 1 modelMap.set(model, existing) } const modelSummary = Array.from(modelMap.entries()) .map(([model, data]) => ({ model, ...data })) .sort((a, b) => b.quota - a.quota) const pdfBuffer = await generateBillingPDF({ siteName: site.name, siteUrl: site.url, userId: userInfo.id, username: userInfo.username, group: userInfo.group || 'default', startDate, endDate, totalQuota, totalRequests: allLogs.length, modelSummary, logs: allLogs }) res.setHeader('Content-Type', 'application/pdf') res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(site.name)}_billing_${startDate}_${endDate}.pdf"`) res.send(pdfBuffer) } catch (error: any) { res.status(500).json({ success: false, message: `Failed to generate report: ${error.message}` }) } }) // POST /api/billing/export/csv router.post('/export/csv', sessionAuth, async (req: Request, res: Response) => { const session = req.session! const { startDate, endDate } = req.body const site = db.prepare('SELECT name FROM sites WHERE id = ?').get(session.site_id) as any try { const startTs = Math.floor(new Date(startDate).getTime() / 1000) const endTs = Math.floor(new Date(endDate).getTime() / 1000) let allLogs: any[] = [] let page = 1 let hasMore = true while (hasMore) { const logRes = await fetch( `${session.site_url}/api/log/self?start_timestamp=${startTs}&end_timestamp=${endTs}&type=2&p=${page}&page_size=100`, { headers: { 'Authorization': session.access_token } } ) const logData = await logRes.json() as any if (!logData.success || !logData.data?.items?.length) { hasMore = false } else { allLogs = allLogs.concat(logData.data.items) hasMore = logData.data.items.length === 100 page++ } if (allLogs.length > 10000) hasMore = false } const BOM = '\uFEFF' const headers = ['Time', 'Model', 'Token', 'Quota', 'Cost(USD)', 'Prompt Tokens', 'Completion Tokens', 'Request ID'] const rows = allLogs.map(log => [ new Date((log.created_at || 0) * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }), log.model_name || '', log.token_name || '', log.quota || 0, ((log.quota || 0) / 500000).toFixed(6), log.prompt_tokens || 0, log.completion_tokens || 0, log.request_id || '' ]) const csv = BOM + [headers, ...rows].map(row => row.map(cell => `"${cell}"`).join(',')).join('\n') res.setHeader('Content-Type', 'text/csv; charset=utf-8') res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(site.name)}_billing_${startDate}_${endDate}.csv"`) res.send(csv) } catch (error: any) { res.status(500).json({ success: false, message: `Failed to generate CSV: ${error.message}` }) } }) // POST /api/billing/verify — verify HMAC signature router.post('/verify', (req: Request, res: Response) => { const { userId, startDate, endDate, totalQuota, totalRecords, generatedAt, signature } = req.body try { const valid = verifyHmacSignature({ userId, startDate, endDate, totalQuota, totalRecords, generatedAt, signature }) res.json({ success: true, data: { valid } }) } catch { res.json({ success: true, data: { valid: false } }) } }) export default router