From 78f47389f3111d2b2d29f2cefb96d66836afbd1c Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 24 Mar 2026 23:10:20 +0100 Subject: [PATCH] feat: implement API versioning (v1) and restore missing routes --- backend/src/routes/v1/index.ts | 135 ++++++++++++++++++++++++ backend/src/services/reminder-engine.ts | 3 + client/components/pages/settings.tsx | 9 +- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 backend/src/routes/v1/index.ts diff --git a/backend/src/routes/v1/index.ts b/backend/src/routes/v1/index.ts new file mode 100644 index 0000000..149c86f --- /dev/null +++ b/backend/src/routes/v1/index.ts @@ -0,0 +1,135 @@ +import express from 'express'; +import subscriptionRoutes from '../subscriptions'; +import riskScoreRoutes from '../risk-score'; +import simulationRoutes from '../simulation'; +import merchantRoutes from '../merchants'; +import teamRoutes from '../team'; +import digestRoutes from '../digest'; +import pushNotificationsRoutes from '../push-notifications'; +import userRoutes from '../user'; +import integrationRoutes from '../integrations'; + +import { schedulerService } from '../../services/scheduler'; +import { reminderEngine } from '../../services/reminder-engine'; +import { monitoringService } from '../../services/monitoring-service'; +import { healthService } from '../../services/health-service'; +import { expiryService } from '../../services/expiry-service'; +import { adminAuth } from '../../middleware/admin'; +import logger from '../../config/logger'; + +const v1Router = express.Router(); + +// Standard API Routes +v1Router.use('/subscriptions', subscriptionRoutes); +v1Router.use('/risk-score', riskScoreRoutes); +v1Router.use('/simulation', simulationRoutes); +v1Router.use('/merchants', merchantRoutes); +v1Router.use('/team', teamRoutes); +v1Router.use('/digest', digestRoutes); +v1Router.use('/push-notifications', pushNotificationsRoutes); +v1Router.use('/user', userRoutes); +v1Router.use('/integrations', integrationRoutes); + +// Auth alias (some parts of frontend might use /api/v1/auth) +v1Router.use('/auth', userRoutes); + +// Reminders Routes +v1Router.get('/reminders/status', (req: express.Request, res: express.Response) => { + const status = schedulerService.getStatus(); + res.json(status); +}); + +v1Router.post('/reminders/process', adminAuth, async (req: express.Request, res: express.Response) => { + try { + await reminderEngine.processReminders(); + res.json({ success: true, message: 'Reminders processed' }); + } catch (error) { + logger.error('Error processing reminders:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } +}); + +v1Router.post('/reminders/schedule', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const daysBefore = req.body.daysBefore || [7, 3, 1]; + await reminderEngine.scheduleReminders(daysBefore); + res.json({ success: true, message: 'Reminders scheduled' }); + } catch (error) { + logger.error('Error scheduling reminders:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } +}); + +v1Router.post('/reminders/retry', adminAuth, async (req: express.Request, res: express.Response) => { + try { + await reminderEngine.processRetries(); + res.json({ success: true, message: 'Retries processed' }); + } catch (error) { + logger.error('Error processing retries:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } +}); + +// Admin Metrics Endpoints +v1Router.get('/admin/metrics/subscriptions', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const metrics = await monitoringService.getSubscriptionMetrics(); + res.json(metrics); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch subscription metrics' }); + } +}); + +v1Router.get('/admin/metrics/renewals', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const metrics = await monitoringService.getRenewalMetrics(); + res.json(metrics); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch renewal metrics' }); + } +}); + +v1Router.get('/admin/metrics/activity', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const metrics = await monitoringService.getAgentActivity(); + res.json(metrics); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch agent activity' }); + } +}); + +v1Router.get('/admin/health', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const includeHistory = req.query.history !== 'false'; + const health = await healthService.getAdminHealth(includeHistory); + const statusCode = health.status === 'unhealthy' ? 503 : 200; + res.status(statusCode).json(health); + } catch (error) { + logger.error('Error fetching admin health:', error); + res.status(500).json({ error: 'Failed to fetch health status' }); + } +}); + +v1Router.post('/admin/expiry/process', adminAuth, async (req: express.Request, res: express.Response) => { + try { + const result = await expiryService.processExpiries(); + res.json({ success: true, data: result }); + } catch (error) { + logger.error('Error processing expiries:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error), + }); + } +}); + +export default v1Router; diff --git a/backend/src/services/reminder-engine.ts b/backend/src/services/reminder-engine.ts index 05b9612..fd5a14b 100644 --- a/backend/src/services/reminder-engine.ts +++ b/backend/src/services/reminder-engine.ts @@ -164,6 +164,8 @@ export class ReminderEngine { } } + + await blockchainService.logReminderEvent( reminder.user_id, payload, @@ -285,6 +287,7 @@ export class ReminderEngine { if (!result.success && result.metadata?.retryable === false) { await this.removeStalePushSubscription(delivery.user_id); } + } else { await this.markDeliveryAsFailed(delivery.id, `Unknown channel: ${delivery.channel}`); return; diff --git a/client/components/pages/settings.tsx b/client/components/pages/settings.tsx index 30112f7..d24fdcb 100644 --- a/client/components/pages/settings.tsx +++ b/client/components/pages/settings.tsx @@ -12,8 +12,10 @@ import { DollarSign, Users, Building2, + Send, } from "lucide-react" -import { useState } from "react" +import React, { useState, useEffect } from "react" +import { apiGet, apiPatch } from "@/lib/api" import { type Currency, CURRENCY_NAMES, CURRENCY_SYMBOLS } from "@/lib/currency-utils" interface SettingsPageProps { @@ -43,6 +45,7 @@ export default function SettingsPage({ const [emailAlerts, setEmailAlerts] = useState(true) const [weeklyReports, setWeeklyReports] = useState(true) const [recommendations, setRecommendations] = useState(true) + const [initialLoad, setInitialLoad] = useState(true) const [showAddApiKey, setShowAddApiKey] = useState(false) const [newApiKey, setNewApiKey] = useState({ tool: "", key: "" }) const [apiKeys, setApiKeys] = useState([ @@ -180,6 +183,8 @@ export default function SettingsPage({ setEmailAccounts(emailAccounts.map((e) => (e.id === id ? { ...e, lastScanned: "Just now" } : e))) } + + const handleUpgradeToTeam = () => { if (!teamSetup.workspaceName || !teamSetup.workDomain) { alert("Please fill in workspace name and work domain") @@ -600,6 +605,8 @@ export default function SettingsPage({ + + {/* API Keys Management */}