@nordsym/apiclaw 1.7.2 → 1.7.4
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.
- package/convex/_generated/api.d.ts +115 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/adminActivate.d.ts +3 -0
- package/convex/adminActivate.d.ts.map +1 -0
- package/convex/adminActivate.js +47 -0
- package/convex/adminActivate.js.map +1 -0
- package/convex/adminActivate.ts +54 -0
- package/convex/adminStats.d.ts +3 -0
- package/convex/adminStats.d.ts.map +1 -0
- package/convex/adminStats.js +42 -0
- package/convex/adminStats.js.map +1 -0
- package/convex/adminStats.ts +44 -0
- package/convex/agents.d.ts +76 -0
- package/convex/agents.d.ts.map +1 -0
- package/convex/agents.js +699 -0
- package/convex/agents.js.map +1 -0
- package/convex/agents.ts +814 -0
- package/convex/analytics.d.ts +5 -0
- package/convex/analytics.d.ts.map +1 -0
- package/convex/analytics.js +166 -0
- package/convex/analytics.js.map +1 -0
- package/convex/analytics.ts +186 -0
- package/convex/billing.d.ts +88 -0
- package/convex/billing.d.ts.map +1 -0
- package/convex/billing.js +655 -0
- package/convex/billing.js.map +1 -0
- package/convex/billing.ts +791 -0
- package/convex/capabilities.d.ts +9 -0
- package/convex/capabilities.d.ts.map +1 -0
- package/convex/capabilities.js +145 -0
- package/convex/capabilities.js.map +1 -0
- package/convex/capabilities.ts +157 -0
- package/convex/chains.d.ts +68 -0
- package/convex/chains.d.ts.map +1 -0
- package/convex/chains.js +1105 -0
- package/convex/chains.js.map +1 -0
- package/convex/chains.ts +1318 -0
- package/convex/credits.d.ts +25 -0
- package/convex/credits.d.ts.map +1 -0
- package/convex/credits.js +186 -0
- package/convex/credits.js.map +1 -0
- package/convex/credits.ts +211 -0
- package/convex/crons.d.ts +3 -0
- package/convex/crons.d.ts.map +1 -0
- package/convex/crons.js +17 -0
- package/convex/crons.js.map +1 -0
- package/convex/crons.ts +28 -0
- package/convex/directCall.d.ts +72 -0
- package/convex/directCall.d.ts.map +1 -0
- package/convex/directCall.js +627 -0
- package/convex/directCall.js.map +1 -0
- package/convex/directCall.ts +678 -0
- package/convex/earnProgress.d.ts +58 -0
- package/convex/earnProgress.d.ts.map +1 -0
- package/convex/earnProgress.js +649 -0
- package/convex/earnProgress.js.map +1 -0
- package/convex/earnProgress.ts +753 -0
- package/convex/email.d.ts +14 -0
- package/convex/email.d.ts.map +1 -0
- package/convex/email.js +300 -0
- package/convex/email.js.map +1 -0
- package/convex/email.ts +329 -0
- package/convex/feedback.d.ts +7 -0
- package/convex/feedback.d.ts.map +1 -0
- package/convex/feedback.js +227 -0
- package/convex/feedback.js.map +1 -0
- package/convex/feedback.ts +265 -0
- package/convex/http.d.ts +3 -0
- package/convex/http.d.ts.map +1 -0
- package/convex/http.js +1405 -0
- package/convex/http.js.map +1 -0
- package/convex/http.ts +1577 -0
- package/convex/inbound.d.ts +2 -0
- package/convex/inbound.d.ts.map +1 -0
- package/convex/inbound.js +32 -0
- package/convex/inbound.js.map +1 -0
- package/convex/inbound.ts +32 -0
- package/convex/logs.d.ts +38 -0
- package/convex/logs.d.ts.map +1 -0
- package/convex/logs.js +487 -0
- package/convex/logs.js.map +1 -0
- package/convex/logs.ts +550 -0
- package/convex/mou.d.ts +6 -0
- package/convex/mou.d.ts.map +1 -0
- package/convex/mou.js +82 -0
- package/convex/mou.js.map +1 -0
- package/convex/mou.ts +91 -0
- package/convex/providerKeys.d.ts +31 -0
- package/convex/providerKeys.d.ts.map +1 -0
- package/convex/providerKeys.js +257 -0
- package/convex/providerKeys.js.map +1 -0
- package/convex/providerKeys.ts +289 -0
- package/convex/providers.d.ts +32 -0
- package/convex/providers.d.ts.map +1 -0
- package/convex/providers.js +814 -0
- package/convex/providers.js.map +1 -0
- package/convex/providers.ts +909 -0
- package/convex/purchases.d.ts +7 -0
- package/convex/purchases.d.ts.map +1 -0
- package/convex/purchases.js +157 -0
- package/convex/purchases.js.map +1 -0
- package/convex/purchases.ts +183 -0
- package/convex/ratelimit.d.ts +4 -0
- package/convex/ratelimit.d.ts.map +1 -0
- package/convex/ratelimit.js +91 -0
- package/convex/ratelimit.js.map +1 -0
- package/convex/ratelimit.ts +104 -0
- package/convex/schema.ts +802 -0
- package/convex/searchLogs.d.ts +4 -0
- package/convex/searchLogs.d.ts.map +1 -0
- package/convex/searchLogs.js +129 -0
- package/convex/searchLogs.js.map +1 -0
- package/convex/searchLogs.ts +146 -0
- package/convex/seedAPILayerAPIs.d.ts +7 -0
- package/convex/seedAPILayerAPIs.d.ts.map +1 -0
- package/convex/seedAPILayerAPIs.js +177 -0
- package/convex/seedAPILayerAPIs.js.map +1 -0
- package/convex/seedAPILayerAPIs.ts +191 -0
- package/convex/seedDirectCallConfigs.d.ts +2 -0
- package/convex/seedDirectCallConfigs.d.ts.map +1 -0
- package/convex/seedDirectCallConfigs.js +324 -0
- package/convex/seedDirectCallConfigs.js.map +1 -0
- package/convex/seedDirectCallConfigs.ts +336 -0
- package/convex/seedPratham.d.ts +6 -0
- package/convex/seedPratham.d.ts.map +1 -0
- package/convex/seedPratham.js +150 -0
- package/convex/seedPratham.js.map +1 -0
- package/convex/seedPratham.ts +161 -0
- package/convex/spendAlerts.d.ts +36 -0
- package/convex/spendAlerts.d.ts.map +1 -0
- package/convex/spendAlerts.js +380 -0
- package/convex/spendAlerts.js.map +1 -0
- package/convex/spendAlerts.ts +442 -0
- package/convex/stripeActions.d.ts +19 -0
- package/convex/stripeActions.d.ts.map +1 -0
- package/convex/stripeActions.js +411 -0
- package/convex/stripeActions.js.map +1 -0
- package/convex/stripeActions.ts +512 -0
- package/convex/teams.d.ts +21 -0
- package/convex/teams.d.ts.map +1 -0
- package/convex/teams.js +215 -0
- package/convex/teams.js.map +1 -0
- package/convex/teams.ts +243 -0
- package/convex/telemetry.d.ts +4 -0
- package/convex/telemetry.d.ts.map +1 -0
- package/convex/telemetry.js +74 -0
- package/convex/telemetry.js.map +1 -0
- package/convex/telemetry.ts +81 -0
- package/convex/tsconfig.json +25 -0
- package/convex/updateAPIStatus.d.ts +6 -0
- package/convex/updateAPIStatus.d.ts.map +1 -0
- package/convex/updateAPIStatus.js +40 -0
- package/convex/updateAPIStatus.js.map +1 -0
- package/convex/updateAPIStatus.ts +45 -0
- package/convex/usage.d.ts +27 -0
- package/convex/usage.d.ts.map +1 -0
- package/convex/usage.js +229 -0
- package/convex/usage.js.map +1 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.d.ts +4 -0
- package/convex/waitlist.d.ts.map +1 -0
- package/convex/waitlist.js +49 -0
- package/convex/waitlist.js.map +1 -0
- package/convex/waitlist.ts +55 -0
- package/convex/webhooks.d.ts +12 -0
- package/convex/webhooks.d.ts.map +1 -0
- package/convex/webhooks.js +410 -0
- package/convex/webhooks.js.map +1 -0
- package/convex/webhooks.ts +494 -0
- package/convex/workspaces.d.ts +31 -0
- package/convex/workspaces.d.ts.map +1 -0
- package/convex/workspaces.js +975 -0
- package/convex/workspaces.js.map +1 -0
- package/convex/workspaces.ts +1130 -0
- package/dist/bin.js +0 -0
- package/dist/capability-router.js +1 -1
- package/dist/capability-router.js.map +1 -1
- package/dist/execute.d.ts +2 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +18 -4
- package/dist/execute.js.map +1 -1
- package/dist/http-api.js +1 -1
- package/dist/http-api.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-analytics.d.ts +32 -0
- package/dist/mcp-analytics.d.ts.map +1 -0
- package/dist/mcp-analytics.js +130 -0
- package/dist/mcp-analytics.js.map +1 -0
- package/package.json +1 -1
- package/dist/chain-types.d.ts +0 -187
- package/dist/chain-types.d.ts.map +0 -1
- package/dist/chain-types.js +0 -33
- package/dist/chain-types.js.map +0 -1
- package/dist/registry/apis.json.bak +0 -248811
- package/dist/src/bin.js +0 -17
- package/dist/src/capability-router.js +0 -240
- package/dist/src/chainExecutor.js +0 -451
- package/dist/src/chainResolver.js +0 -518
- package/dist/src/cli/commands/doctor.js +0 -324
- package/dist/src/cli/commands/mcp-install.js +0 -255
- package/dist/src/cli/commands/restore.js +0 -259
- package/dist/src/cli/commands/setup.js +0 -205
- package/dist/src/cli/commands/uninstall.js +0 -188
- package/dist/src/cli/index.js +0 -111
- package/dist/src/cli.js +0 -302
- package/dist/src/confirmation.js +0 -240
- package/dist/src/credentials.js +0 -357
- package/dist/src/credits.js +0 -260
- package/dist/src/crypto.js +0 -66
- package/dist/src/discovery.js +0 -504
- package/dist/src/enterprise/env.js +0 -123
- package/dist/src/enterprise/script-generator.js +0 -460
- package/dist/src/execute-dynamic.js +0 -473
- package/dist/src/execute.js +0 -1727
- package/dist/src/index.js +0 -2062
- package/dist/src/metered.js +0 -80
- package/dist/src/open-apis.js +0 -276
- package/dist/src/proxy.js +0 -28
- package/dist/src/session.js +0 -86
- package/dist/src/stripe.js +0 -407
- package/dist/src/telemetry.js +0 -49
- package/dist/src/types.js +0 -2
- package/dist/src/utils/backup.js +0 -181
- package/dist/src/utils/config.js +0 -220
- package/dist/src/utils/os.js +0 -105
- package/dist/src/utils/paths.js +0 -159
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
import { mutation, query } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
// Register a new provider and their first API
|
|
5
|
+
export const registerProvider = mutation({
|
|
6
|
+
args: {
|
|
7
|
+
provider: v.object({
|
|
8
|
+
name: v.string(),
|
|
9
|
+
email: v.string(),
|
|
10
|
+
website: v.optional(v.string()),
|
|
11
|
+
}),
|
|
12
|
+
api: v.object({
|
|
13
|
+
name: v.string(),
|
|
14
|
+
description: v.string(),
|
|
15
|
+
category: v.string(),
|
|
16
|
+
openApiUrl: v.optional(v.string()),
|
|
17
|
+
docsUrl: v.optional(v.string()),
|
|
18
|
+
pricingModel: v.string(),
|
|
19
|
+
pricingNotes: v.optional(v.string()),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
handler: async (ctx, args) => {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
|
|
25
|
+
// Check if provider already exists by email
|
|
26
|
+
const existing = await ctx.db
|
|
27
|
+
.query("providers")
|
|
28
|
+
.withIndex("by_email", (q) => q.eq("email", args.provider.email))
|
|
29
|
+
.first();
|
|
30
|
+
|
|
31
|
+
let providerId;
|
|
32
|
+
|
|
33
|
+
if (existing) {
|
|
34
|
+
// Use existing provider
|
|
35
|
+
providerId = existing._id;
|
|
36
|
+
} else {
|
|
37
|
+
// Create new provider - auto-approve for now
|
|
38
|
+
providerId = await ctx.db.insert("providers", {
|
|
39
|
+
name: args.provider.name,
|
|
40
|
+
email: args.provider.email,
|
|
41
|
+
website: args.provider.website,
|
|
42
|
+
status: "approved", // Auto-approve for MVP
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
approvedAt: now,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create the API listing - auto-approve for now
|
|
50
|
+
const apiId = await ctx.db.insert("providerAPIs", {
|
|
51
|
+
providerId,
|
|
52
|
+
name: args.api.name,
|
|
53
|
+
description: args.api.description,
|
|
54
|
+
category: args.api.category,
|
|
55
|
+
openApiUrl: args.api.openApiUrl,
|
|
56
|
+
docsUrl: args.api.docsUrl,
|
|
57
|
+
pricingModel: args.api.pricingModel,
|
|
58
|
+
pricingNotes: args.api.pricingNotes,
|
|
59
|
+
status: "approved", // Auto-approve for MVP
|
|
60
|
+
createdAt: now,
|
|
61
|
+
approvedAt: now,
|
|
62
|
+
discoveryCount: 0,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Create session for auto-login after registration
|
|
66
|
+
const sessionToken = generateToken();
|
|
67
|
+
const sessionExpiresAt = now + 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
68
|
+
|
|
69
|
+
await ctx.db.insert("sessions", {
|
|
70
|
+
providerId,
|
|
71
|
+
token: sessionToken,
|
|
72
|
+
expiresAt: sessionExpiresAt,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return { providerId, apiId, sessionToken };
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Get provider by email
|
|
81
|
+
export const getProviderByEmail = query({
|
|
82
|
+
args: { email: v.string() },
|
|
83
|
+
handler: async (ctx, args) => {
|
|
84
|
+
return await ctx.db
|
|
85
|
+
.query("providers")
|
|
86
|
+
.withIndex("by_email", (q) => q.eq("email", args.email))
|
|
87
|
+
.first();
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Get all APIs for a provider
|
|
92
|
+
export const getProviderAPIs = query({
|
|
93
|
+
args: { providerId: v.id("providers") },
|
|
94
|
+
handler: async (ctx, args) => {
|
|
95
|
+
return await ctx.db
|
|
96
|
+
.query("providerAPIs")
|
|
97
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", args.providerId))
|
|
98
|
+
.collect();
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Get all approved APIs (for the registry)
|
|
103
|
+
export const getApprovedAPIs = query({
|
|
104
|
+
args: {
|
|
105
|
+
category: v.optional(v.string()),
|
|
106
|
+
limit: v.optional(v.number()),
|
|
107
|
+
},
|
|
108
|
+
handler: async (ctx, args) => {
|
|
109
|
+
const query = ctx.db
|
|
110
|
+
.query("providerAPIs")
|
|
111
|
+
.withIndex("by_status", (q) => q.eq("status", "approved"));
|
|
112
|
+
|
|
113
|
+
const apis = await query.collect();
|
|
114
|
+
|
|
115
|
+
// Filter by category if provided
|
|
116
|
+
let filtered = args.category
|
|
117
|
+
? apis.filter((api) => api.category === args.category)
|
|
118
|
+
: apis;
|
|
119
|
+
|
|
120
|
+
// Apply limit
|
|
121
|
+
if (args.limit) {
|
|
122
|
+
filtered = filtered.slice(0, args.limit);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return filtered;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Get API categories with counts
|
|
130
|
+
export const getCategories = query({
|
|
131
|
+
handler: async (ctx) => {
|
|
132
|
+
const apis = await ctx.db
|
|
133
|
+
.query("providerAPIs")
|
|
134
|
+
.withIndex("by_status", (q) => q.eq("status", "approved"))
|
|
135
|
+
.collect();
|
|
136
|
+
|
|
137
|
+
const categories: Record<string, number> = {};
|
|
138
|
+
for (const api of apis) {
|
|
139
|
+
categories[api.category] = (categories[api.category] || 0) + 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return Object.entries(categories)
|
|
143
|
+
.map(([name, count]) => ({ name, count }))
|
|
144
|
+
.sort((a, b) => b.count - a.count);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Increment discovery count when an agent finds an API
|
|
149
|
+
export const trackDiscovery = mutation({
|
|
150
|
+
args: { apiId: v.id("providerAPIs") },
|
|
151
|
+
handler: async (ctx, args) => {
|
|
152
|
+
const api = await ctx.db.get(args.apiId);
|
|
153
|
+
if (!api) return;
|
|
154
|
+
|
|
155
|
+
await ctx.db.patch(args.apiId, {
|
|
156
|
+
discoveryCount: (api.discoveryCount || 0) + 1,
|
|
157
|
+
lastDiscoveredAt: Date.now(),
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Admin: List pending providers
|
|
163
|
+
export const getPendingProviders = query({
|
|
164
|
+
handler: async (ctx) => {
|
|
165
|
+
return await ctx.db
|
|
166
|
+
.query("providers")
|
|
167
|
+
.withIndex("by_status", (q) => q.eq("status", "pending"))
|
|
168
|
+
.collect();
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Admin: Approve provider
|
|
173
|
+
export const approveProvider = mutation({
|
|
174
|
+
args: { providerId: v.id("providers") },
|
|
175
|
+
handler: async (ctx, args) => {
|
|
176
|
+
await ctx.db.patch(args.providerId, {
|
|
177
|
+
status: "approved",
|
|
178
|
+
approvedAt: Date.now(),
|
|
179
|
+
updatedAt: Date.now(),
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Admin: Reject provider
|
|
185
|
+
export const rejectProvider = mutation({
|
|
186
|
+
args: { providerId: v.id("providers") },
|
|
187
|
+
handler: async (ctx, args) => {
|
|
188
|
+
await ctx.db.patch(args.providerId, {
|
|
189
|
+
status: "rejected",
|
|
190
|
+
updatedAt: Date.now(),
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Get provider stats
|
|
196
|
+
export const getProviderStats = query({
|
|
197
|
+
handler: async (ctx) => {
|
|
198
|
+
const providers = await ctx.db.query("providers").collect();
|
|
199
|
+
const apis = await ctx.db.query("providerAPIs").collect();
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
totalProviders: providers.length,
|
|
203
|
+
approvedProviders: providers.filter((p) => p.status === "approved").length,
|
|
204
|
+
pendingProviders: providers.filter((p) => p.status === "pending").length,
|
|
205
|
+
totalAPIs: apis.length,
|
|
206
|
+
approvedAPIs: apis.filter((a) => a.status === "approved").length,
|
|
207
|
+
pendingAPIs: apis.filter((a) => a.status === "pending").length,
|
|
208
|
+
totalDiscoveries: apis.reduce((sum, a) => sum + (a.discoveryCount || 0), 0),
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================
|
|
214
|
+
// DASHBOARD AUTH & SESSION FUNCTIONS
|
|
215
|
+
// ============================================
|
|
216
|
+
|
|
217
|
+
// Create magic link for email auth
|
|
218
|
+
export const createMagicLink = mutation({
|
|
219
|
+
args: { email: v.string() },
|
|
220
|
+
handler: async (ctx, { email }) => {
|
|
221
|
+
const token = generateToken();
|
|
222
|
+
const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
|
|
223
|
+
|
|
224
|
+
await ctx.db.insert("magicLinks", {
|
|
225
|
+
email: email.toLowerCase(),
|
|
226
|
+
token,
|
|
227
|
+
expiresAt,
|
|
228
|
+
createdAt: Date.now(),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return { token, expiresAt };
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Verify magic link and create session
|
|
236
|
+
export const verifyMagicLink = mutation({
|
|
237
|
+
args: { token: v.string() },
|
|
238
|
+
handler: async (ctx, { token }) => {
|
|
239
|
+
const magicLink = await ctx.db
|
|
240
|
+
.query("magicLinks")
|
|
241
|
+
.withIndex("by_token", (q) => q.eq("token", token))
|
|
242
|
+
.first();
|
|
243
|
+
|
|
244
|
+
if (!magicLink) {
|
|
245
|
+
return { success: false, error: "Invalid token" };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (magicLink.expiresAt < Date.now()) {
|
|
249
|
+
return { success: false, error: "Token expired" };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (magicLink.usedAt) {
|
|
253
|
+
return { success: false, error: "Token already used" };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Mark as used
|
|
257
|
+
await ctx.db.patch(magicLink._id, { usedAt: Date.now() });
|
|
258
|
+
|
|
259
|
+
// Find or create provider
|
|
260
|
+
let provider = await ctx.db
|
|
261
|
+
.query("providers")
|
|
262
|
+
.withIndex("by_email", (q) => q.eq("email", magicLink.email))
|
|
263
|
+
.first();
|
|
264
|
+
|
|
265
|
+
if (!provider) {
|
|
266
|
+
const providerId = await ctx.db.insert("providers", {
|
|
267
|
+
email: magicLink.email,
|
|
268
|
+
name: magicLink.email.split("@")[0],
|
|
269
|
+
status: "approved",
|
|
270
|
+
createdAt: Date.now(),
|
|
271
|
+
updatedAt: Date.now(),
|
|
272
|
+
});
|
|
273
|
+
provider = await ctx.db.get(providerId);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Create session
|
|
277
|
+
const sessionToken = generateToken();
|
|
278
|
+
const sessionExpiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
279
|
+
|
|
280
|
+
await ctx.db.insert("sessions", {
|
|
281
|
+
providerId: provider!._id,
|
|
282
|
+
token: sessionToken,
|
|
283
|
+
expiresAt: sessionExpiresAt,
|
|
284
|
+
createdAt: Date.now(),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
sessionToken,
|
|
290
|
+
provider: {
|
|
291
|
+
id: provider!._id,
|
|
292
|
+
email: provider!.email,
|
|
293
|
+
name: provider!.name,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Get current session
|
|
300
|
+
export const getSession = query({
|
|
301
|
+
args: { token: v.string() },
|
|
302
|
+
handler: async (ctx, { token }) => {
|
|
303
|
+
const session = await ctx.db
|
|
304
|
+
.query("sessions")
|
|
305
|
+
.withIndex("by_token", (q) => q.eq("token", token))
|
|
306
|
+
.first();
|
|
307
|
+
|
|
308
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const provider = await ctx.db.get(session.providerId);
|
|
313
|
+
if (!provider) return null;
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
providerId: provider._id,
|
|
317
|
+
email: provider.email,
|
|
318
|
+
name: provider.name,
|
|
319
|
+
stripeOnboardingComplete: (provider as any).stripeOnboardingComplete,
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// ============================================
|
|
325
|
+
// DASHBOARD ANALYTICS
|
|
326
|
+
// ============================================
|
|
327
|
+
|
|
328
|
+
// Get single API by ID
|
|
329
|
+
export const getApiById = query({
|
|
330
|
+
args: { apiId: v.string() },
|
|
331
|
+
handler: async (ctx, args) => {
|
|
332
|
+
// Try to get by document ID
|
|
333
|
+
try {
|
|
334
|
+
const api = await ctx.db.get(args.apiId as any);
|
|
335
|
+
if (api) {
|
|
336
|
+
// Check if it has Direct Call configured
|
|
337
|
+
const directCall = await ctx.db
|
|
338
|
+
.query("providerDirectCall")
|
|
339
|
+
.filter((q) => q.eq(q.field("apiId"), args.apiId))
|
|
340
|
+
.first();
|
|
341
|
+
return { ...api, hasDirectCall: !!directCall, directCallStatus: directCall?.status };
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
// Not a valid ID format
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Get provider APIs with Direct Call status
|
|
351
|
+
export const getProviderAPIsWithStatus = query({
|
|
352
|
+
args: { providerId: v.string() },
|
|
353
|
+
handler: async (ctx, args) => {
|
|
354
|
+
const apis = await ctx.db
|
|
355
|
+
.query("providerAPIs")
|
|
356
|
+
.filter((q) => q.eq(q.field("providerId"), args.providerId as any))
|
|
357
|
+
.collect();
|
|
358
|
+
|
|
359
|
+
// Add Direct Call status to each API
|
|
360
|
+
const apisWithStatus = await Promise.all(
|
|
361
|
+
apis.map(async (api) => {
|
|
362
|
+
const directCall = await ctx.db
|
|
363
|
+
.query("providerDirectCall")
|
|
364
|
+
.filter((q) => q.eq(q.field("apiId"), api._id))
|
|
365
|
+
.first();
|
|
366
|
+
return {
|
|
367
|
+
...api,
|
|
368
|
+
hasDirectCall: !!directCall,
|
|
369
|
+
directCallStatus: directCall?.status,
|
|
370
|
+
};
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
return apisWithStatus;
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// DEBUG: Delete API
|
|
379
|
+
export const debugDeleteAPI = mutation({
|
|
380
|
+
args: { apiId: v.string() },
|
|
381
|
+
handler: async (ctx, args) => {
|
|
382
|
+
await ctx.db.delete(args.apiId as any);
|
|
383
|
+
return { deleted: true };
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Add API for logged-in provider (used by register page)
|
|
388
|
+
export const addAPI = mutation({
|
|
389
|
+
args: {
|
|
390
|
+
token: v.string(),
|
|
391
|
+
api: v.object({
|
|
392
|
+
name: v.string(),
|
|
393
|
+
description: v.string(),
|
|
394
|
+
category: v.string(),
|
|
395
|
+
openApiUrl: v.optional(v.string()),
|
|
396
|
+
docsUrl: v.optional(v.string()),
|
|
397
|
+
pricingModel: v.string(),
|
|
398
|
+
pricingNotes: v.optional(v.string()),
|
|
399
|
+
}),
|
|
400
|
+
},
|
|
401
|
+
handler: async (ctx, args) => {
|
|
402
|
+
// Verify session
|
|
403
|
+
const session = await ctx.db
|
|
404
|
+
.query("sessions")
|
|
405
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
406
|
+
.first();
|
|
407
|
+
|
|
408
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
409
|
+
throw new Error("Invalid or expired session");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
const apiId = await ctx.db.insert("providerAPIs", {
|
|
414
|
+
providerId: session.providerId,
|
|
415
|
+
name: args.api.name,
|
|
416
|
+
description: args.api.description,
|
|
417
|
+
category: args.api.category,
|
|
418
|
+
openApiUrl: args.api.openApiUrl,
|
|
419
|
+
docsUrl: args.api.docsUrl,
|
|
420
|
+
pricingModel: args.api.pricingModel,
|
|
421
|
+
pricingNotes: args.api.pricingNotes,
|
|
422
|
+
status: "approved",
|
|
423
|
+
createdAt: now,
|
|
424
|
+
approvedAt: now,
|
|
425
|
+
discoveryCount: 0,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return { apiId, success: true };
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Delete API for logged-in provider
|
|
433
|
+
export const deleteAPI = mutation({
|
|
434
|
+
args: {
|
|
435
|
+
token: v.string(),
|
|
436
|
+
apiId: v.string(),
|
|
437
|
+
},
|
|
438
|
+
handler: async (ctx, args) => {
|
|
439
|
+
// Verify session
|
|
440
|
+
const session = await ctx.db
|
|
441
|
+
.query("sessions")
|
|
442
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
443
|
+
.first();
|
|
444
|
+
|
|
445
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
446
|
+
throw new Error("Invalid or expired session");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Get the API and verify ownership
|
|
450
|
+
const api = await ctx.db.get(args.apiId as any);
|
|
451
|
+
if (!api || (api as any).providerId !== session.providerId) {
|
|
452
|
+
throw new Error("API not found or unauthorized");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Delete the API
|
|
456
|
+
await ctx.db.delete(args.apiId as any);
|
|
457
|
+
|
|
458
|
+
// Also delete any Direct Call config
|
|
459
|
+
const directCallConfig = await ctx.db
|
|
460
|
+
.query("providerDirectCall")
|
|
461
|
+
.filter((q) => q.eq(q.field("apiId"), args.apiId))
|
|
462
|
+
.first();
|
|
463
|
+
if (directCallConfig) {
|
|
464
|
+
await ctx.db.delete(directCallConfig._id);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { deleted: true };
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// DEBUG: Update provider name
|
|
472
|
+
export const debugUpdateProvider = mutation({
|
|
473
|
+
args: {
|
|
474
|
+
providerId: v.string(),
|
|
475
|
+
name: v.optional(v.string()),
|
|
476
|
+
},
|
|
477
|
+
handler: async (ctx, args) => {
|
|
478
|
+
const updates: any = {};
|
|
479
|
+
if (args.name) updates.name = args.name;
|
|
480
|
+
await ctx.db.patch(args.providerId as any, updates);
|
|
481
|
+
return { updated: true };
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// DEBUG: Add API for provider (seeding)
|
|
486
|
+
export const debugAddAPI = mutation({
|
|
487
|
+
args: {
|
|
488
|
+
providerId: v.string(),
|
|
489
|
+
name: v.string(),
|
|
490
|
+
description: v.string(),
|
|
491
|
+
category: v.string(),
|
|
492
|
+
docsUrl: v.optional(v.string()),
|
|
493
|
+
pricingModel: v.string(),
|
|
494
|
+
pricingNotes: v.optional(v.string()),
|
|
495
|
+
},
|
|
496
|
+
handler: async (ctx, args) => {
|
|
497
|
+
const now = Date.now();
|
|
498
|
+
return await ctx.db.insert("providerAPIs", {
|
|
499
|
+
providerId: args.providerId as any,
|
|
500
|
+
name: args.name,
|
|
501
|
+
description: args.description,
|
|
502
|
+
category: args.category,
|
|
503
|
+
docsUrl: args.docsUrl,
|
|
504
|
+
pricingModel: args.pricingModel,
|
|
505
|
+
pricingNotes: args.pricingNotes,
|
|
506
|
+
status: "approved",
|
|
507
|
+
createdAt: now,
|
|
508
|
+
approvedAt: now,
|
|
509
|
+
discoveryCount: 0,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// DEBUG: Delete provider and all related data
|
|
515
|
+
export const debugDeleteProvider = mutation({
|
|
516
|
+
args: { providerId: v.string() },
|
|
517
|
+
handler: async (ctx, args) => {
|
|
518
|
+
const providerId = args.providerId as any;
|
|
519
|
+
|
|
520
|
+
// Delete sessions
|
|
521
|
+
const sessions = await ctx.db.query("sessions").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
522
|
+
for (const s of sessions) await ctx.db.delete(s._id);
|
|
523
|
+
|
|
524
|
+
// Delete APIs
|
|
525
|
+
const apis = await ctx.db.query("providerAPIs").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
526
|
+
for (const a of apis) await ctx.db.delete(a._id);
|
|
527
|
+
|
|
528
|
+
// Delete direct call configs
|
|
529
|
+
const configs = await ctx.db.query("providerDirectCall").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
530
|
+
for (const c of configs) {
|
|
531
|
+
// Delete actions for this config
|
|
532
|
+
const actions = await ctx.db.query("providerActions").filter(q => q.eq(q.field("directCallId"), c._id)).collect();
|
|
533
|
+
for (const act of actions) await ctx.db.delete(act._id);
|
|
534
|
+
await ctx.db.delete(c._id);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Delete provider
|
|
538
|
+
await ctx.db.delete(providerId);
|
|
539
|
+
|
|
540
|
+
return { deleted: true };
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// DEBUG: List all sessions
|
|
545
|
+
export const debugListSessions = query({
|
|
546
|
+
args: {},
|
|
547
|
+
handler: async (ctx) => {
|
|
548
|
+
return await ctx.db.query("sessions").collect();
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// DEBUG: List all providers
|
|
553
|
+
export const debugListProviders = query({
|
|
554
|
+
args: {},
|
|
555
|
+
handler: async (ctx) => {
|
|
556
|
+
return await ctx.db.query("providers").collect();
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
export const getAnalytics = query({
|
|
561
|
+
args: {
|
|
562
|
+
token: v.string(),
|
|
563
|
+
period: v.optional(v.string()), // "week", "month", "all"
|
|
564
|
+
},
|
|
565
|
+
handler: async (ctx, { token, period = "month" }) => {
|
|
566
|
+
const session = await ctx.db
|
|
567
|
+
.query("sessions")
|
|
568
|
+
.withIndex("by_token", (q) => q.eq("token", token))
|
|
569
|
+
.first();
|
|
570
|
+
|
|
571
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const now = Date.now();
|
|
576
|
+
const periodMs = {
|
|
577
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
578
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
579
|
+
all: now,
|
|
580
|
+
}[period] || 30 * 24 * 60 * 60 * 1000;
|
|
581
|
+
|
|
582
|
+
const startTime = now - periodMs;
|
|
583
|
+
|
|
584
|
+
// Get usage logs for this provider (from Direct Call usageLog)
|
|
585
|
+
const usageLogs = await ctx.db
|
|
586
|
+
.query("usageLog")
|
|
587
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
588
|
+
.collect();
|
|
589
|
+
|
|
590
|
+
const periodCalls = usageLogs.filter((c) => c.timestamp >= startTime);
|
|
591
|
+
|
|
592
|
+
// Calculate metrics
|
|
593
|
+
const totalCalls = periodCalls.length;
|
|
594
|
+
const uniqueAgents = new Set(periodCalls.map((c) => c.userId)).size;
|
|
595
|
+
const totalRevenue = periodCalls.reduce((sum, c) => sum + (c.creditsUsed / 100), 0); // cents to dollars
|
|
596
|
+
const successCount = periodCalls.filter((c) => c.success).length;
|
|
597
|
+
const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 100;
|
|
598
|
+
const avgLatency = totalCalls > 0
|
|
599
|
+
? periodCalls.reduce((sum, c) => sum + c.latencyMs, 0) / totalCalls
|
|
600
|
+
: 0;
|
|
601
|
+
|
|
602
|
+
// Calls over time (daily buckets)
|
|
603
|
+
const callsByDay: Record<string, { calls: number; revenue: number; success: number }> = {};
|
|
604
|
+
|
|
605
|
+
periodCalls.forEach((call) => {
|
|
606
|
+
const day = new Date(call.timestamp).toISOString().split("T")[0];
|
|
607
|
+
if (!callsByDay[day]) {
|
|
608
|
+
callsByDay[day] = { calls: 0, revenue: 0, success: 0 };
|
|
609
|
+
}
|
|
610
|
+
callsByDay[day].calls += 1;
|
|
611
|
+
callsByDay[day].revenue += call.creditsUsed / 100;
|
|
612
|
+
if (call.success) callsByDay[day].success += 1;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Top agents (users)
|
|
616
|
+
const agentCallCounts: Record<string, number> = {};
|
|
617
|
+
periodCalls.forEach((call) => {
|
|
618
|
+
agentCallCounts[call.userId] = (agentCallCounts[call.userId] || 0) + 1;
|
|
619
|
+
});
|
|
620
|
+
const topAgents = Object.entries(agentCallCounts)
|
|
621
|
+
.sort((a, b) => b[1] - a[1])
|
|
622
|
+
.slice(0, 10)
|
|
623
|
+
.map(([agentId, calls]) => ({ agentId, calls }));
|
|
624
|
+
|
|
625
|
+
// Top actions
|
|
626
|
+
const actionCallCounts: Record<string, number> = {};
|
|
627
|
+
periodCalls.forEach((call) => {
|
|
628
|
+
actionCallCounts[call.actionName] = (actionCallCounts[call.actionName] || 0) + 1;
|
|
629
|
+
});
|
|
630
|
+
const topActions = Object.entries(actionCallCounts)
|
|
631
|
+
.sort((a, b) => b[1] - a[1])
|
|
632
|
+
.slice(0, 10)
|
|
633
|
+
.map(([actionName, calls]) => ({ actionName, calls }));
|
|
634
|
+
|
|
635
|
+
// Get provider's APIs
|
|
636
|
+
const apis = await ctx.db
|
|
637
|
+
.query("providerAPIs")
|
|
638
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
639
|
+
.collect();
|
|
640
|
+
|
|
641
|
+
// Get Direct Call configs to map directCallId to apiId
|
|
642
|
+
const directCallConfigs = await ctx.db
|
|
643
|
+
.query("providerDirectCall")
|
|
644
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
645
|
+
.collect();
|
|
646
|
+
|
|
647
|
+
// Calls per API (via directCallId → apiId mapping)
|
|
648
|
+
const callsByDirectCallId: Record<string, number> = {};
|
|
649
|
+
periodCalls.forEach((call) => {
|
|
650
|
+
const dcId = call.directCallId as string;
|
|
651
|
+
callsByDirectCallId[dcId] = (callsByDirectCallId[dcId] || 0) + 1;
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// Map to apiId
|
|
655
|
+
const callsByApiId: Record<string, number> = {};
|
|
656
|
+
directCallConfigs.forEach((dc) => {
|
|
657
|
+
if (dc.apiId) {
|
|
658
|
+
callsByApiId[dc.apiId as string] = callsByDirectCallId[dc._id as string] || 0;
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Preview data for providers with no usage yet
|
|
663
|
+
const isPreview = totalCalls === 0;
|
|
664
|
+
|
|
665
|
+
if (isPreview) {
|
|
666
|
+
// Generate preview data so providers can see what the dashboard looks like
|
|
667
|
+
const previewDays = [];
|
|
668
|
+
for (let i = 13; i >= 0; i--) {
|
|
669
|
+
const date = new Date(now - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
|
|
670
|
+
previewDays.push({
|
|
671
|
+
date,
|
|
672
|
+
calls: Math.floor(Math.random() * 50) + 10,
|
|
673
|
+
revenue: Math.random() * 5,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
totalCalls: 847,
|
|
679
|
+
uniqueAgents: 23,
|
|
680
|
+
totalRevenue: 42.35,
|
|
681
|
+
successRate: 98.2,
|
|
682
|
+
avgLatency: 145,
|
|
683
|
+
callsByDay: previewDays,
|
|
684
|
+
topAgents: [
|
|
685
|
+
{ agentId: "agent_demo_1", calls: 234 },
|
|
686
|
+
{ agentId: "agent_demo_2", calls: 189 },
|
|
687
|
+
{ agentId: "agent_demo_3", calls: 156 },
|
|
688
|
+
{ agentId: "agent_demo_4", calls: 98 },
|
|
689
|
+
{ agentId: "agent_demo_5", calls: 67 },
|
|
690
|
+
],
|
|
691
|
+
topActions: [
|
|
692
|
+
{ actionName: "send_message", calls: 412 },
|
|
693
|
+
{ actionName: "get_status", calls: 289 },
|
|
694
|
+
{ actionName: "create_invoice", calls: 146 },
|
|
695
|
+
],
|
|
696
|
+
apis: apis.map((api) => ({
|
|
697
|
+
id: api._id,
|
|
698
|
+
name: api.name,
|
|
699
|
+
calls: Math.floor(Math.random() * 200) + 50,
|
|
700
|
+
status: api.status,
|
|
701
|
+
})),
|
|
702
|
+
isPreview: true,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
totalCalls,
|
|
708
|
+
uniqueAgents,
|
|
709
|
+
totalRevenue,
|
|
710
|
+
successRate,
|
|
711
|
+
avgLatency,
|
|
712
|
+
callsByDay: Object.entries(callsByDay)
|
|
713
|
+
.map(([date, data]) => ({
|
|
714
|
+
date,
|
|
715
|
+
calls: data.calls,
|
|
716
|
+
revenue: data.revenue,
|
|
717
|
+
}))
|
|
718
|
+
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
719
|
+
topAgents,
|
|
720
|
+
topActions,
|
|
721
|
+
apis: apis.map((api) => ({
|
|
722
|
+
id: api._id,
|
|
723
|
+
name: api.name,
|
|
724
|
+
calls: callsByApiId[api._id as string] || 0,
|
|
725
|
+
status: api.status,
|
|
726
|
+
})),
|
|
727
|
+
isPreview: false,
|
|
728
|
+
};
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// ============================================
|
|
733
|
+
// DASHBOARD EARNINGS
|
|
734
|
+
// ============================================
|
|
735
|
+
|
|
736
|
+
export const getEarnings = query({
|
|
737
|
+
args: { token: v.string() },
|
|
738
|
+
handler: async (ctx, { token }) => {
|
|
739
|
+
const session = await ctx.db
|
|
740
|
+
.query("sessions")
|
|
741
|
+
.withIndex("by_token", (q) => q.eq("token", token))
|
|
742
|
+
.first();
|
|
743
|
+
|
|
744
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Get all payouts
|
|
749
|
+
const payouts = await ctx.db
|
|
750
|
+
.query("payouts")
|
|
751
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
752
|
+
.collect();
|
|
753
|
+
|
|
754
|
+
// Get all API calls to calculate pending
|
|
755
|
+
const allCalls = await ctx.db
|
|
756
|
+
.query("apiCalls")
|
|
757
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
758
|
+
.collect();
|
|
759
|
+
|
|
760
|
+
// Find last completed payout
|
|
761
|
+
const completedPayouts = payouts
|
|
762
|
+
.filter((p) => p.status === "completed")
|
|
763
|
+
.sort((a, b) => b.periodEnd - a.periodEnd);
|
|
764
|
+
|
|
765
|
+
const lastPayoutEnd = completedPayouts[0]?.periodEnd || 0;
|
|
766
|
+
|
|
767
|
+
// Pending = all revenue since last payout
|
|
768
|
+
const pendingCalls = allCalls.filter((c) => c.timestamp > lastPayoutEnd);
|
|
769
|
+
const pendingAmount = pendingCalls.reduce((sum, c) => sum + c.costUsd, 0);
|
|
770
|
+
|
|
771
|
+
// Total earned all time
|
|
772
|
+
const totalEarned = allCalls.reduce((sum, c) => sum + c.costUsd, 0);
|
|
773
|
+
|
|
774
|
+
// Get provider for Stripe status
|
|
775
|
+
const provider = await ctx.db.get(session.providerId);
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
pendingAmount,
|
|
779
|
+
totalEarned,
|
|
780
|
+
totalPaidOut: completedPayouts.reduce((sum, p) => sum + p.amountUsd, 0),
|
|
781
|
+
stripeConnected: !!(provider as any)?.stripeConnectId,
|
|
782
|
+
stripeOnboardingComplete: (provider as any)?.stripeOnboardingComplete || false,
|
|
783
|
+
payouts: payouts
|
|
784
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
785
|
+
.slice(0, 20)
|
|
786
|
+
.map((p) => ({
|
|
787
|
+
id: p._id,
|
|
788
|
+
amount: p.amountUsd,
|
|
789
|
+
status: p.status,
|
|
790
|
+
periodStart: p.periodStart,
|
|
791
|
+
periodEnd: p.periodEnd,
|
|
792
|
+
createdAt: p.createdAt,
|
|
793
|
+
completedAt: p.completedAt,
|
|
794
|
+
})),
|
|
795
|
+
};
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// ============================================
|
|
800
|
+
// ADMIN QUERIES
|
|
801
|
+
// ============================================
|
|
802
|
+
|
|
803
|
+
// Get all providers (admin only)
|
|
804
|
+
export const getAllProviders = query({
|
|
805
|
+
handler: async (ctx) => {
|
|
806
|
+
return await ctx.db
|
|
807
|
+
.query("providers")
|
|
808
|
+
.order("desc")
|
|
809
|
+
.collect();
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Get all APIs (admin only)
|
|
814
|
+
export const getAllAPIs = query({
|
|
815
|
+
handler: async (ctx) => {
|
|
816
|
+
return await ctx.db
|
|
817
|
+
.query("providerAPIs")
|
|
818
|
+
.order("desc")
|
|
819
|
+
.collect();
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Helper function
|
|
824
|
+
function generateToken(): string {
|
|
825
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
826
|
+
let result = "";
|
|
827
|
+
for (let i = 0; i < 48; i++) {
|
|
828
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
829
|
+
}
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Debug: Update API name/description
|
|
834
|
+
export const debugUpdateAPI = mutation({
|
|
835
|
+
args: {
|
|
836
|
+
apiId: v.string(),
|
|
837
|
+
name: v.optional(v.string()),
|
|
838
|
+
description: v.optional(v.string()),
|
|
839
|
+
category: v.optional(v.string()),
|
|
840
|
+
status: v.optional(v.string()),
|
|
841
|
+
hasDirectCall: v.optional(v.boolean()),
|
|
842
|
+
},
|
|
843
|
+
handler: async (ctx, args) => {
|
|
844
|
+
const updates: any = {};
|
|
845
|
+
if (args.name) updates.name = args.name;
|
|
846
|
+
if (args.description) updates.description = args.description;
|
|
847
|
+
if (args.category) updates.category = args.category;
|
|
848
|
+
if (args.status) updates.status = args.status;
|
|
849
|
+
if (args.hasDirectCall !== undefined) updates.hasDirectCall = args.hasDirectCall;
|
|
850
|
+
await ctx.db.patch(args.apiId as any, updates);
|
|
851
|
+
return { updated: true };
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// ─── Workspace-native API management (no provider account needed) ─────────────
|
|
856
|
+
|
|
857
|
+
// Get all APIs listed by a workspace
|
|
858
|
+
export const getByWorkspaceId = query({
|
|
859
|
+
args: { workspaceId: v.id("workspaces") },
|
|
860
|
+
handler: async (ctx, { workspaceId }) => {
|
|
861
|
+
return await ctx.db
|
|
862
|
+
.query("providerAPIs")
|
|
863
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
|
|
864
|
+
.collect();
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// List a new API directly from a workspace — no provider registration
|
|
869
|
+
export const createForWorkspace = mutation({
|
|
870
|
+
args: {
|
|
871
|
+
workspaceId: v.id("workspaces"),
|
|
872
|
+
name: v.string(),
|
|
873
|
+
description: v.string(),
|
|
874
|
+
category: v.string(),
|
|
875
|
+
openApiUrl: v.optional(v.string()),
|
|
876
|
+
docsUrl: v.optional(v.string()),
|
|
877
|
+
pricingModel: v.string(),
|
|
878
|
+
pricingNotes: v.optional(v.string()),
|
|
879
|
+
},
|
|
880
|
+
handler: async (ctx, args) => {
|
|
881
|
+
const id = await ctx.db.insert("providerAPIs", {
|
|
882
|
+
workspaceId: args.workspaceId,
|
|
883
|
+
name: args.name,
|
|
884
|
+
description: args.description,
|
|
885
|
+
category: args.category,
|
|
886
|
+
openApiUrl: args.openApiUrl,
|
|
887
|
+
docsUrl: args.docsUrl,
|
|
888
|
+
pricingModel: args.pricingModel,
|
|
889
|
+
pricingNotes: args.pricingNotes,
|
|
890
|
+
status: "active",
|
|
891
|
+
createdAt: Date.now(),
|
|
892
|
+
discoveryCount: 0,
|
|
893
|
+
});
|
|
894
|
+
return { id };
|
|
895
|
+
},
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// Delete an API owned by a workspace
|
|
899
|
+
export const deleteForWorkspace = mutation({
|
|
900
|
+
args: { apiId: v.id("providerAPIs"), workspaceId: v.id("workspaces") },
|
|
901
|
+
handler: async (ctx, { apiId, workspaceId }) => {
|
|
902
|
+
const api = await ctx.db.get(apiId);
|
|
903
|
+
if (!api || api.workspaceId !== workspaceId) {
|
|
904
|
+
throw new Error("Not found or unauthorized");
|
|
905
|
+
}
|
|
906
|
+
await ctx.db.delete(apiId);
|
|
907
|
+
return { deleted: true };
|
|
908
|
+
},
|
|
909
|
+
});
|