feat: production readiness — 3-extension deploy with security hardening and observability

- Strip extensions to enable-banking, ai-categorization, ai-chat only
- Remove push-notifications cron from vercel.json
- Add security headers (HSTS, CSP, X-Frame-Options, Permissions-Policy)
- Add /api/health endpoint for uptime monitoring
- Add env var validation in ensureInitialized()
- Fix SIE4 #IB opening balance records from year-end closing entry
- Replace in-memory ai-chat rate limiter with Supabase-backed distributed rate limiting
- Add Sentry error tracking scaffolding (@sentry/nextjs, instrumentation hook)
- Add AI token usage tracking (migration 047, usage-tracker, wired into both AI extensions)
- Include pending enable-banking and dashboard improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-02 11:32:43 +01:00
co-authored by Claude Opus 4.6
parent 1a1c6ba40f
commit 13725ffc16
33 changed files with 2852 additions and 271 deletions
+27 -6
View File
@@ -140,22 +140,42 @@ export default async function DashboardPage() {
(inv) => inv.status === 'overdue'
).length
// Fetch bank balance (if connected)
// Fetch bank balance and consent info (if connected)
const { data: bankConnections } = await supabase
.from('bank_connections')
.select('accounts, status')
.select('id, accounts_data, status, consent_expires, bank_name')
.eq('user_id', user.id)
.eq('status', 'active')
.limit(1)
let bankBalance: number | null = null
if (bankConnections && bankConnections.length > 0) {
const accounts = bankConnections[0].accounts as { balance: number }[] | null
if (accounts && accounts.length > 0) {
bankBalance = accounts.reduce((sum, acc) => sum + (acc.balance || 0), 0)
const allBalances = bankConnections.flatMap(conn => {
const accounts = conn.accounts_data as { balance: number }[] | null
return accounts || []
})
if (allBalances.length > 0) {
bankBalance = allBalances.reduce((sum, acc) => sum + (acc.balance || 0), 0)
}
}
// Compute expiring bank connections (consent expires within 14 days)
const nowMs = new Date().getTime()
const expiringBankConnections = (bankConnections || [])
.filter(conn => {
if (!conn.consent_expires) return false
const daysLeft = Math.ceil(
(new Date(conn.consent_expires).getTime() - nowMs) / (1000 * 60 * 60 * 24)
)
return daysLeft > 0 && daysLeft <= 14
})
.map(conn => ({
id: conn.id as string,
bank_name: conn.bank_name as string,
days_left: Math.ceil(
(new Date(conn.consent_expires!).getTime() - nowMs) / (1000 * 60 * 60 * 24)
),
}))
// Fetch upcoming deadlines (next 7 days + overdue)
const today = new Date().toISOString().split('T')[0]
const nextWeek = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
@@ -274,6 +294,7 @@ export default async function DashboardPage() {
unpaidVatTotal,
overdueInvoicesCount: overdueCount,
bankBalance,
expiringBankConnections,
deadlines: (deadlines || []) as Deadline[],
receiptQueue,
missingUnderlagCount,
+23 -2
View File
@@ -63,9 +63,30 @@ export default function SettingsPage() {
if (bankConnected === 'true') {
toast({
title: 'Bank ansluten!',
description: 'Din bank är nu kopplad och transaktioner kan hämtas.',
description: 'Din bank är nu kopplad. Transaktioner hämtas...',
})
router.replace('/settings')
// Auto-sync transactions after connection
const connectionId = searchParams.get('connection_id')
if (connectionId) {
fetch('/api/extensions/ext/enable-banking/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ connection_id: connectionId, days_back: 90 }),
})
.then(res => res.json())
.then(data => {
if (data.imported > 0) {
toast({
title: 'Transaktioner hämtade',
description: `${data.imported} transaktioner importerade`,
})
}
})
.catch(() => {})
}
router.replace('/settings?tab=banking')
}
if (bankError) {