upfynai-code 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,163 @@
1
1
  import { Router } from 'express';
2
2
  import { getProjects, getSessions } from '../projects.js';
3
+ import { userDb, subscriptionDb, paymentDb, apiKeysDb, credentialsDb, relayTokensDb, webhookDb, workflowDb } from '../database/db.js';
3
4
 
4
5
  const router = Router();
5
6
 
7
+ // AI provider credential types
8
+ const AI_PROVIDER_TYPES = ['anthropic_key', 'openai_key', 'openrouter_key', 'google_key'];
9
+ const PROVIDER_LABELS = {
10
+ anthropic_key: 'Anthropic',
11
+ openai_key: 'OpenAI',
12
+ openrouter_key: 'OpenRouter',
13
+ google_key: 'Google',
14
+ };
15
+
16
+ /**
17
+ * GET /api/dashboard/user-stats — Per-user dashboard data from Turso
18
+ * Aggregates: account, subscription, payments, API keys, AI providers, relay tokens, webhooks, workflows
19
+ */
20
+ router.get('/user-stats', async (req, res) => {
21
+ try {
22
+ const userId = req.user.id;
23
+
24
+ // Run all DB queries in parallel
25
+ const [
26
+ fullUser,
27
+ activeSub,
28
+ allSubs,
29
+ payments,
30
+ apiKeys,
31
+ credentials,
32
+ relayTokens,
33
+ webhooks,
34
+ workflows,
35
+ ] = await Promise.all([
36
+ userDb.getUserById(userId),
37
+ subscriptionDb.getActiveSub(userId).catch(() => null),
38
+ subscriptionDb.getAllSubs(userId).catch(() => []),
39
+ paymentDb.getUserPayments(userId).catch(() => []),
40
+ apiKeysDb.getApiKeys(userId).catch(() => []),
41
+ credentialsDb.getCredentials(userId).catch(() => []),
42
+ relayTokensDb.getTokens(userId).catch(() => []),
43
+ webhookDb.getAll(userId).catch(() => []),
44
+ workflowDb.getAll(userId).catch(() => []),
45
+ ]);
46
+
47
+ // Expire overdue subs
48
+ try { await subscriptionDb.expireOverdue(); } catch { /* non-critical */ }
49
+
50
+ // Account
51
+ const account = fullUser ? {
52
+ username: fullUser.username,
53
+ email: fullUser.email,
54
+ phone: fullUser.phone,
55
+ firstName: fullUser.first_name,
56
+ lastName: fullUser.last_name,
57
+ userCode: fullUser.user_code,
58
+ createdAt: fullUser.created_at,
59
+ lastLogin: fullUser.last_login,
60
+ accessOverride: fullUser.access_override,
61
+ } : null;
62
+
63
+ // Subscription
64
+ const subscription = {
65
+ active: activeSub ? {
66
+ id: activeSub.id,
67
+ planId: activeSub.plan_id,
68
+ status: activeSub.status,
69
+ startsAt: activeSub.starts_at,
70
+ expiresAt: activeSub.expires_at,
71
+ amount: activeSub.amount,
72
+ currency: activeSub.currency || 'INR',
73
+ } : null,
74
+ history: allSubs.map(s => ({
75
+ id: s.id,
76
+ planId: s.plan_id,
77
+ status: s.status,
78
+ startsAt: s.starts_at,
79
+ expiresAt: s.expires_at,
80
+ amount: s.amount,
81
+ currency: s.currency || 'INR',
82
+ createdAt: s.created_at,
83
+ })),
84
+ };
85
+
86
+ // Payments
87
+ const paidPayments = payments.filter(p => p.status === 'paid');
88
+ const totalAmountPaise = paidPayments.reduce((sum, p) => sum + (Number(p.amount) || 0), 0);
89
+ const paymentData = {
90
+ total: payments.length,
91
+ paid: paidPayments.length,
92
+ totalAmountPaise,
93
+ recent: payments.slice(0, 10).map(p => ({
94
+ id: p.id,
95
+ planId: p.plan_id,
96
+ amount: p.amount,
97
+ currency: p.currency || 'INR',
98
+ status: p.status,
99
+ createdAt: p.created_at,
100
+ })),
101
+ };
102
+
103
+ // API Keys
104
+ const activeKeys = apiKeys.filter(k => k.is_active);
105
+ const apiKeyData = {
106
+ total: apiKeys.length,
107
+ active: activeKeys.length,
108
+ };
109
+
110
+ // AI Providers (filter credentials by AI provider types)
111
+ const aiCreds = credentials.filter(c => AI_PROVIDER_TYPES.includes(c.credential_type));
112
+ const activeAiCreds = aiCreds.filter(c => c.is_active);
113
+ const aiProviderData = {
114
+ total: aiCreds.length,
115
+ active: activeAiCreds.length,
116
+ providers: activeAiCreds.map(c => PROVIDER_LABELS[c.credential_type] || c.credential_type),
117
+ };
118
+
119
+ // Relay Tokens
120
+ const activeTokens = relayTokens.filter(t => t.is_active);
121
+ const lastConnected = relayTokens
122
+ .filter(t => t.last_connected)
123
+ .sort((a, b) => new Date(b.last_connected) - new Date(a.last_connected))[0]?.last_connected || null;
124
+ const relayData = {
125
+ total: relayTokens.length,
126
+ active: activeTokens.length,
127
+ lastConnected,
128
+ };
129
+
130
+ // Webhooks
131
+ const activeWebhooks = webhooks.filter(w => w.is_active);
132
+ const webhookData = {
133
+ total: webhooks.length,
134
+ active: activeWebhooks.length,
135
+ };
136
+
137
+ // Workflows
138
+ const activeWorkflows = workflows.filter(w => w.is_active);
139
+ const workflowData = {
140
+ total: workflows.length,
141
+ active: activeWorkflows.length,
142
+ };
143
+
144
+ res.json({
145
+ account,
146
+ subscription,
147
+ payments: paymentData,
148
+ apiKeys: apiKeyData,
149
+ aiProviders: aiProviderData,
150
+ relayTokens: relayData,
151
+ webhooks: webhookData,
152
+ workflows: workflowData,
153
+ });
154
+ } catch (error) {
155
+ res.status(500).json({ error: 'Failed to fetch user stats' });
156
+ }
157
+ });
158
+
6
159
  /**
7
- * GET /api/dashboard/stats — Dashboard usage analytics
160
+ * GET /api/dashboard/stats — Dashboard usage analytics (local projects)
8
161
  * Returns session counts, provider breakdown, and today's activity.
9
162
  */
10
163
  router.get('/stats', async (req, res) => {