costhawk 1.0.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.
Files changed (171) hide show
  1. package/.claude/settings.local.json +32 -0
  2. package/STRATEGIC_PLAN_2025-12-01.md +934 -0
  3. package/costcanary/.claude/settings.local.json +9 -0
  4. package/costcanary/.env.production.template +38 -0
  5. package/costcanary/.eslintrc.json +22 -0
  6. package/costcanary/.nvmrc +1 -0
  7. package/costcanary/.prettierignore +11 -0
  8. package/costcanary/.prettierrc.json +12 -0
  9. package/costcanary/ADMIN_SETUP.md +68 -0
  10. package/costcanary/CLAUDE.md +228 -0
  11. package/costcanary/CLERK_SETUP.md +69 -0
  12. package/costcanary/DATABASE_SETUP.md +136 -0
  13. package/costcanary/DEMO_CHECKLIST.md +62 -0
  14. package/costcanary/DEPLOYMENT.md +31 -0
  15. package/costcanary/PRODUCTION_RECOVERY.md +109 -0
  16. package/costcanary/README.md +247 -0
  17. package/costcanary/STRIPE_SECURITY_AUDIT.md +123 -0
  18. package/costcanary/TESTING_ADMIN.md +92 -0
  19. package/costcanary/app/(auth)/sign-in/[[...sign-in]]/page.tsx +25 -0
  20. package/costcanary/app/(auth)/sign-up/[[...sign-up]]/page.tsx +25 -0
  21. package/costcanary/app/(dashboard)/dashboard/admin/page.tsx +260 -0
  22. package/costcanary/app/(dashboard)/dashboard/alerts/page.tsx +64 -0
  23. package/costcanary/app/(dashboard)/dashboard/api-keys/page.tsx +231 -0
  24. package/costcanary/app/(dashboard)/dashboard/billing/page.tsx +349 -0
  25. package/costcanary/app/(dashboard)/dashboard/layout.tsx +188 -0
  26. package/costcanary/app/(dashboard)/dashboard/page.tsx +13 -0
  27. package/costcanary/app/(dashboard)/dashboard/playground/page.tsx +605 -0
  28. package/costcanary/app/(dashboard)/dashboard/settings/page.tsx +86 -0
  29. package/costcanary/app/(dashboard)/dashboard/usage/page.tsx +354 -0
  30. package/costcanary/app/(dashboard)/dashboard/wrapped-keys/page.tsx +677 -0
  31. package/costcanary/app/(marketing)/page.tsx +90 -0
  32. package/costcanary/app/(marketing)/pricing/page.tsx +272 -0
  33. package/costcanary/app/admin/pricing-status/page.tsx +338 -0
  34. package/costcanary/app/api/admin/check-pricing/route.ts +127 -0
  35. package/costcanary/app/api/admin/debug/route.ts +44 -0
  36. package/costcanary/app/api/admin/fix-pricing/route.ts +216 -0
  37. package/costcanary/app/api/admin/pricing-jobs/[jobId]/route.ts +48 -0
  38. package/costcanary/app/api/admin/pricing-jobs/route.ts +45 -0
  39. package/costcanary/app/api/admin/trigger-pricing/route.ts +209 -0
  40. package/costcanary/app/api/admin/whoami/route.ts +44 -0
  41. package/costcanary/app/api/auth/clerk/[...nextjs]/route.ts +93 -0
  42. package/costcanary/app/api/debug/wrapped-key/route.ts +51 -0
  43. package/costcanary/app/api/debug-status/route.ts +9 -0
  44. package/costcanary/app/api/debug-version/route.ts +12 -0
  45. package/costcanary/app/api/health/route.ts +14 -0
  46. package/costcanary/app/api/health-simple/route.ts +18 -0
  47. package/costcanary/app/api/keys/route.ts +162 -0
  48. package/costcanary/app/api/keys/wrapped/[id]/revoke/route.ts +86 -0
  49. package/costcanary/app/api/keys/wrapped/[id]/rotate/route.ts +81 -0
  50. package/costcanary/app/api/keys/wrapped/route.ts +241 -0
  51. package/costcanary/app/api/optimizer/preview/route.ts +147 -0
  52. package/costcanary/app/api/optimizer/route.ts +118 -0
  53. package/costcanary/app/api/pricing/models/route.ts +102 -0
  54. package/costcanary/app/api/proxy/[...path]/route.ts +391 -0
  55. package/costcanary/app/api/proxy/anthropic/route.ts +539 -0
  56. package/costcanary/app/api/proxy/google/route.ts +395 -0
  57. package/costcanary/app/api/proxy/openai/route.ts +529 -0
  58. package/costcanary/app/api/simple-test/route.ts +7 -0
  59. package/costcanary/app/api/stripe/checkout/route.ts +201 -0
  60. package/costcanary/app/api/stripe/webhook/route.ts +392 -0
  61. package/costcanary/app/api/test-connection/route.ts +209 -0
  62. package/costcanary/app/api/test-proxy/route.ts +7 -0
  63. package/costcanary/app/api/test-simple/route.ts +20 -0
  64. package/costcanary/app/api/usage/current/route.ts +112 -0
  65. package/costcanary/app/api/usage/stats/route.ts +129 -0
  66. package/costcanary/app/api/usage/stream/route.ts +113 -0
  67. package/costcanary/app/api/usage/summary/route.ts +67 -0
  68. package/costcanary/app/api/usage/trend/route.ts +119 -0
  69. package/costcanary/app/api/ws/route.ts +23 -0
  70. package/costcanary/app/globals.css +280 -0
  71. package/costcanary/app/layout.tsx +87 -0
  72. package/costcanary/components/Header.tsx +85 -0
  73. package/costcanary/components/dashboard/AddApiKeyModal.tsx +264 -0
  74. package/costcanary/components/dashboard/dashboard-content.tsx +329 -0
  75. package/costcanary/components/landing/DashboardPreview.tsx +222 -0
  76. package/costcanary/components/landing/Features.tsx +238 -0
  77. package/costcanary/components/landing/Footer.tsx +83 -0
  78. package/costcanary/components/landing/Hero.tsx +193 -0
  79. package/costcanary/components/landing/Pricing.tsx +250 -0
  80. package/costcanary/components/landing/Testimonials.tsx +248 -0
  81. package/costcanary/components/theme-provider.tsx +8 -0
  82. package/costcanary/components/ui/alert.tsx +59 -0
  83. package/costcanary/components/ui/badge.tsx +36 -0
  84. package/costcanary/components/ui/button.tsx +56 -0
  85. package/costcanary/components/ui/card.tsx +79 -0
  86. package/costcanary/components/ui/dialog.tsx +122 -0
  87. package/costcanary/components/ui/input.tsx +22 -0
  88. package/costcanary/components/ui/label.tsx +26 -0
  89. package/costcanary/components/ui/progress.tsx +28 -0
  90. package/costcanary/components/ui/select.tsx +160 -0
  91. package/costcanary/components/ui/separator.tsx +31 -0
  92. package/costcanary/components/ui/switch.tsx +29 -0
  93. package/costcanary/components/ui/tabs.tsx +55 -0
  94. package/costcanary/components/ui/toast.tsx +127 -0
  95. package/costcanary/components/ui/toaster.tsx +35 -0
  96. package/costcanary/components/ui/use-toast.ts +189 -0
  97. package/costcanary/components.json +17 -0
  98. package/costcanary/debug-wrapped-keys.md +117 -0
  99. package/costcanary/fix-console.sh +30 -0
  100. package/costcanary/lib/admin-auth.ts +226 -0
  101. package/costcanary/lib/admin-security.ts +124 -0
  102. package/costcanary/lib/audit-events.ts +62 -0
  103. package/costcanary/lib/audit.ts +158 -0
  104. package/costcanary/lib/chart-colors.ts +152 -0
  105. package/costcanary/lib/cost-calculator.ts +212 -0
  106. package/costcanary/lib/db-utils.ts +325 -0
  107. package/costcanary/lib/db.ts +14 -0
  108. package/costcanary/lib/encryption.ts +120 -0
  109. package/costcanary/lib/kms.ts +358 -0
  110. package/costcanary/lib/model-alias.ts +180 -0
  111. package/costcanary/lib/pricing.ts +292 -0
  112. package/costcanary/lib/prisma.ts +52 -0
  113. package/costcanary/lib/railway-db.ts +157 -0
  114. package/costcanary/lib/sse-parser.ts +283 -0
  115. package/costcanary/lib/stripe-client.ts +81 -0
  116. package/costcanary/lib/stripe-server.ts +52 -0
  117. package/costcanary/lib/tokens.ts +396 -0
  118. package/costcanary/lib/usage-limits.ts +164 -0
  119. package/costcanary/lib/utils.ts +6 -0
  120. package/costcanary/lib/websocket.ts +153 -0
  121. package/costcanary/lib/wrapped-keys.ts +531 -0
  122. package/costcanary/market-research.md +443 -0
  123. package/costcanary/middleware.ts +48 -0
  124. package/costcanary/next.config.js +43 -0
  125. package/costcanary/nia-sources.md +151 -0
  126. package/costcanary/package-lock.json +12162 -0
  127. package/costcanary/package.json +92 -0
  128. package/costcanary/package.json.backup +89 -0
  129. package/costcanary/postcss.config.js +6 -0
  130. package/costcanary/pricing-worker/.env.example +8 -0
  131. package/costcanary/pricing-worker/README.md +81 -0
  132. package/costcanary/pricing-worker/package-lock.json +1109 -0
  133. package/costcanary/pricing-worker/package.json +26 -0
  134. package/costcanary/pricing-worker/railway.json +13 -0
  135. package/costcanary/pricing-worker/schema.prisma +326 -0
  136. package/costcanary/pricing-worker/src/index.ts +115 -0
  137. package/costcanary/pricing-worker/src/services/pricing-updater.ts +79 -0
  138. package/costcanary/pricing-worker/src/services/tavily-client.ts +474 -0
  139. package/costcanary/pricing-worker/test-tavily.ts +47 -0
  140. package/costcanary/pricing-worker/tsconfig.json +24 -0
  141. package/costcanary/prisma/migrations/001_add_stripe_fields.sql +26 -0
  142. package/costcanary/prisma/schema.prisma +326 -0
  143. package/costcanary/prisma/seed-pricing.ts +133 -0
  144. package/costcanary/public/costhawk-logo.png +0 -0
  145. package/costcanary/railway.json +30 -0
  146. package/costcanary/railway.toml +16 -0
  147. package/costcanary/research-nia.md +298 -0
  148. package/costcanary/research.md +411 -0
  149. package/costcanary/scripts/build-production.js +65 -0
  150. package/costcanary/scripts/check-current-pricing.ts +51 -0
  151. package/costcanary/scripts/check-pricing-data.ts +174 -0
  152. package/costcanary/scripts/create-stripe-prices.js +49 -0
  153. package/costcanary/scripts/fix-pricing-data.ts +135 -0
  154. package/costcanary/scripts/fix-pricing-db.ts +148 -0
  155. package/costcanary/scripts/postinstall.js +58 -0
  156. package/costcanary/scripts/railway-deploy.sh +52 -0
  157. package/costcanary/scripts/run-migration.js +61 -0
  158. package/costcanary/scripts/start-production.js +175 -0
  159. package/costcanary/scripts/test-wrapped-key.ts +85 -0
  160. package/costcanary/scripts/validate-deployment.js +176 -0
  161. package/costcanary/scripts/validate-production.js +119 -0
  162. package/costcanary/server.js.backup +38 -0
  163. package/costcanary/tailwind.config.ts +216 -0
  164. package/costcanary/test-pricing-status.sh +27 -0
  165. package/costcanary/tsconfig.json +42 -0
  166. package/docs/sessions/session-2025-12-01.md +570 -0
  167. package/executive-summary.md +302 -0
  168. package/index.js +1 -0
  169. package/nia-sources.md +163 -0
  170. package/package.json +16 -0
  171. package/research.md +750 -0
@@ -0,0 +1,338 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { Button } from '@/components/ui/button';
7
+ import { RefreshCw, CheckCircle, XCircle, Clock, AlertCircle } from 'lucide-react';
8
+ import { formatDistanceToNow } from 'date-fns';
9
+
10
+ interface PricingJob {
11
+ id: string;
12
+ status: string;
13
+ createdAt: string;
14
+ completedAt?: string;
15
+ metadata?: Record<string, unknown>;
16
+ error?: string;
17
+ }
18
+
19
+ interface ActivePricing {
20
+ id: string;
21
+ provider: string;
22
+ isActive: boolean;
23
+ modelCount: number;
24
+ createdAt: string;
25
+ note?: string;
26
+ }
27
+
28
+ interface SamplePrice {
29
+ provider: string;
30
+ model: string;
31
+ inputPer1k: number;
32
+ outputPer1k: number;
33
+ createdAt: string;
34
+ }
35
+
36
+ interface PricingData {
37
+ recentJobs: PricingJob[];
38
+ activePricing: ActivePricing[];
39
+ counts: {
40
+ totalJobs: number;
41
+ completedJobs: number;
42
+ failedJobs: number;
43
+ totalVersions: number;
44
+ activeVersions: number;
45
+ totalModels: number;
46
+ totalAliases: number;
47
+ };
48
+ recentUpdates: {
49
+ last24Hours: number;
50
+ };
51
+ samplePricing: SamplePrice[];
52
+ }
53
+
54
+ export default function PricingStatusPage() {
55
+ const [data, setData] = useState<PricingData | null>(null);
56
+ const [loading, setLoading] = useState(true);
57
+ const [error, setError] = useState<string | null>(null);
58
+
59
+ const fetchData = async () => {
60
+ setLoading(true);
61
+ setError(null);
62
+ try {
63
+ const response = await fetch('/api/admin/check-pricing');
64
+ if (!response.ok) {
65
+ throw new Error(`HTTP error! status: ${response.status}`);
66
+ }
67
+ const result = await response.json();
68
+ if (result.success) {
69
+ setData(result.data);
70
+ } else {
71
+ throw new Error(result.error || 'Failed to fetch data');
72
+ }
73
+ } catch (err) {
74
+ setError(err instanceof Error ? err.message : 'Failed to fetch pricing data');
75
+ } finally {
76
+ setLoading(false);
77
+ }
78
+ };
79
+
80
+ useEffect(() => {
81
+ fetchData();
82
+ }, []);
83
+
84
+ const getStatusIcon = (status: string) => {
85
+ switch (status) {
86
+ case 'COMPLETED':
87
+ return <CheckCircle className="h-4 w-4 text-green-500" />;
88
+ case 'FAILED':
89
+ return <XCircle className="h-4 w-4 text-red-500" />;
90
+ case 'RUNNING':
91
+ return <Clock className="h-4 w-4 text-blue-500 animate-spin" />;
92
+ case 'PARTIAL':
93
+ return <AlertCircle className="h-4 w-4 text-yellow-500" />;
94
+ default:
95
+ return <Clock className="h-4 w-4 text-gray-500" />;
96
+ }
97
+ };
98
+
99
+ const getStatusBadge = (status: string) => {
100
+ const variants: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
101
+ COMPLETED: 'default',
102
+ FAILED: 'destructive',
103
+ RUNNING: 'secondary',
104
+ PARTIAL: 'outline',
105
+ PENDING: 'outline'
106
+ };
107
+ return (
108
+ <Badge variant={variants[status] || 'outline'} className="flex items-center gap-1">
109
+ {getStatusIcon(status)}
110
+ {status}
111
+ </Badge>
112
+ );
113
+ };
114
+
115
+ if (loading) {
116
+ return (
117
+ <div className="flex items-center justify-center min-h-screen">
118
+ <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900"></div>
119
+ </div>
120
+ );
121
+ }
122
+
123
+ if (error) {
124
+ return (
125
+ <div className="container mx-auto p-6">
126
+ <Card className="border-red-200 bg-red-50">
127
+ <CardHeader>
128
+ <CardTitle className="text-red-800">Error Loading Pricing Data</CardTitle>
129
+ </CardHeader>
130
+ <CardContent>
131
+ <p className="text-red-600">{error}</p>
132
+ <Button onClick={fetchData} className="mt-4">
133
+ <RefreshCw className="mr-2 h-4 w-4" />
134
+ Retry
135
+ </Button>
136
+ </CardContent>
137
+ </Card>
138
+ </div>
139
+ );
140
+ }
141
+
142
+ if (!data) {
143
+ return null;
144
+ }
145
+
146
+ return (
147
+ <div className="container mx-auto p-6 space-y-6">
148
+ <div className="flex justify-between items-center">
149
+ <h1 className="text-3xl font-bold">Pricing Data Status</h1>
150
+ <Button onClick={fetchData} variant="outline">
151
+ <RefreshCw className="mr-2 h-4 w-4" />
152
+ Refresh
153
+ </Button>
154
+ </div>
155
+
156
+ {/* Summary Cards */}
157
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
158
+ <Card>
159
+ <CardHeader className="pb-2">
160
+ <CardTitle className="text-sm font-medium">Total Jobs</CardTitle>
161
+ </CardHeader>
162
+ <CardContent>
163
+ <div className="text-2xl font-bold">{data.counts.totalJobs}</div>
164
+ <div className="text-xs text-muted-foreground">
165
+ {data.counts.completedJobs} completed, {data.counts.failedJobs} failed
166
+ </div>
167
+ </CardContent>
168
+ </Card>
169
+
170
+ <Card>
171
+ <CardHeader className="pb-2">
172
+ <CardTitle className="text-sm font-medium">Pricing Versions</CardTitle>
173
+ </CardHeader>
174
+ <CardContent>
175
+ <div className="text-2xl font-bold">{data.counts.totalVersions}</div>
176
+ <div className="text-xs text-muted-foreground">
177
+ {data.counts.activeVersions} active
178
+ </div>
179
+ </CardContent>
180
+ </Card>
181
+
182
+ <Card>
183
+ <CardHeader className="pb-2">
184
+ <CardTitle className="text-sm font-medium">Model Prices</CardTitle>
185
+ </CardHeader>
186
+ <CardContent>
187
+ <div className="text-2xl font-bold">{data.counts.totalModels}</div>
188
+ <div className="text-xs text-muted-foreground">
189
+ {data.counts.totalAliases} aliases
190
+ </div>
191
+ </CardContent>
192
+ </Card>
193
+
194
+ <Card>
195
+ <CardHeader className="pb-2">
196
+ <CardTitle className="text-sm font-medium">Recent Updates</CardTitle>
197
+ </CardHeader>
198
+ <CardContent>
199
+ <div className="text-2xl font-bold">{data.recentUpdates.last24Hours}</div>
200
+ <div className="text-xs text-muted-foreground">
201
+ in last 24 hours
202
+ </div>
203
+ </CardContent>
204
+ </Card>
205
+ </div>
206
+
207
+ {/* Recent Jobs */}
208
+ <Card>
209
+ <CardHeader>
210
+ <CardTitle>Recent Discovery Jobs</CardTitle>
211
+ <CardDescription>Last 5 pricing discovery jobs</CardDescription>
212
+ </CardHeader>
213
+ <CardContent>
214
+ {data.recentJobs.length === 0 ? (
215
+ <p className="text-muted-foreground">No jobs found</p>
216
+ ) : (
217
+ <div className="space-y-4">
218
+ {data.recentJobs.map((job) => (
219
+ <div key={job.id} className="flex items-center justify-between border-b pb-2">
220
+ <div className="flex items-center gap-4">
221
+ {getStatusBadge(job.status)}
222
+ <div>
223
+ <p className="text-sm font-medium">Job {job.id.slice(0, 8)}...</p>
224
+ <p className="text-xs text-muted-foreground">
225
+ Created {formatDistanceToNow(new Date(job.createdAt))} ago
226
+ </p>
227
+ </div>
228
+ </div>
229
+ <div className="text-right">
230
+ {job.completedAt && (
231
+ <p className="text-xs text-muted-foreground">
232
+ Completed {formatDistanceToNow(new Date(job.completedAt))} ago
233
+ </p>
234
+ )}
235
+ {job.error && (
236
+ <p className="text-xs text-red-500">Error: {job.error}</p>
237
+ )}
238
+ </div>
239
+ </div>
240
+ ))}
241
+ </div>
242
+ )}
243
+ </CardContent>
244
+ </Card>
245
+
246
+ {/* Active Pricing by Provider */}
247
+ <Card>
248
+ <CardHeader>
249
+ <CardTitle>Active Pricing Versions</CardTitle>
250
+ <CardDescription>Currently active pricing data by provider</CardDescription>
251
+ </CardHeader>
252
+ <CardContent>
253
+ {data.activePricing.length === 0 ? (
254
+ <p className="text-muted-foreground">No active pricing versions</p>
255
+ ) : (
256
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
257
+ {data.activePricing.map((pricing) => (
258
+ <div key={pricing.id} className="border rounded-lg p-4">
259
+ <div className="flex items-center justify-between mb-2">
260
+ <Badge variant="outline">{pricing.provider}</Badge>
261
+ {pricing.isActive && (
262
+ <Badge variant="default" className="bg-green-500">Active</Badge>
263
+ )}
264
+ </div>
265
+ <p className="text-sm font-medium">{pricing.modelCount} models</p>
266
+ <p className="text-xs text-muted-foreground">
267
+ Updated {formatDistanceToNow(new Date(pricing.createdAt))} ago
268
+ </p>
269
+ {pricing.note && (
270
+ <p className="text-xs mt-2 italic">{pricing.note}</p>
271
+ )}
272
+ </div>
273
+ ))}
274
+ </div>
275
+ )}
276
+ </CardContent>
277
+ </Card>
278
+
279
+ {/* Current Model Pricing */}
280
+ <Card>
281
+ <CardHeader>
282
+ <CardTitle>Current Model Pricing</CardTitle>
283
+ <CardDescription>Active model prices discovered from provider APIs</CardDescription>
284
+ </CardHeader>
285
+ <CardContent>
286
+ {data.samplePricing.length === 0 ? (
287
+ <p className="text-muted-foreground">No pricing data found</p>
288
+ ) : (
289
+ <div className="overflow-x-auto rounded-lg border">
290
+ <table className="min-w-full divide-y divide-border">
291
+ <thead className="bg-muted/50">
292
+ <tr>
293
+ <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
294
+ Provider
295
+ </th>
296
+ <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
297
+ Model
298
+ </th>
299
+ <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
300
+ Input (per 1K)
301
+ </th>
302
+ <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
303
+ Output (per 1K)
304
+ </th>
305
+ <th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
306
+ Added
307
+ </th>
308
+ </tr>
309
+ </thead>
310
+ <tbody className="divide-y divide-border">
311
+ {data.samplePricing.map((price, idx) => (
312
+ <tr key={idx} className="hover:bg-muted/50 transition-colors">
313
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
314
+ <Badge variant="outline">{price.provider}</Badge>
315
+ </td>
316
+ <td className="px-6 py-4 whitespace-nowrap text-sm font-mono">
317
+ {price.model}
318
+ </td>
319
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
320
+ ${price.inputPer1k.toFixed(4)}
321
+ </td>
322
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
323
+ ${price.outputPer1k.toFixed(4)}
324
+ </td>
325
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
326
+ {formatDistanceToNow(new Date(price.createdAt))} ago
327
+ </td>
328
+ </tr>
329
+ ))}
330
+ </tbody>
331
+ </table>
332
+ </div>
333
+ )}
334
+ </CardContent>
335
+ </Card>
336
+ </div>
337
+ );
338
+ }
@@ -0,0 +1,127 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdminPermission } from '@/lib/admin-auth';
4
+
5
+ export async function GET(_req: NextRequest) {
6
+ try {
7
+ // Check admin permission - use view_sensitive_data as pricing is sensitive
8
+ const admin = await requireAdminPermission('view_sensitive_data');
9
+
10
+ // Get recent jobs
11
+ const recentJobs = await prisma.pricingDiscoveryJob.findMany({
12
+ orderBy: { createdAt: 'desc' },
13
+ take: 5,
14
+ select: {
15
+ id: true,
16
+ status: true,
17
+ createdAt: true,
18
+ completedAt: true,
19
+ metadata: true,
20
+ error: true
21
+ }
22
+ });
23
+
24
+ // Get active pricing versions
25
+ const activePricing = await prisma.pricingVersion.findMany({
26
+ where: { isActive: true },
27
+ include: {
28
+ _count: {
29
+ select: { models: true }
30
+ }
31
+ }
32
+ });
33
+
34
+ // Get counts
35
+ const counts = {
36
+ totalJobs: await prisma.pricingDiscoveryJob.count(),
37
+ completedJobs: await prisma.pricingDiscoveryJob.count({
38
+ where: { status: 'COMPLETED' }
39
+ }),
40
+ failedJobs: await prisma.pricingDiscoveryJob.count({
41
+ where: { status: 'FAILED' }
42
+ }),
43
+ totalVersions: await prisma.pricingVersion.count(),
44
+ activeVersions: await prisma.pricingVersion.count({
45
+ where: { isActive: true }
46
+ }),
47
+ totalModels: await prisma.pricingModelPrice.count(),
48
+ totalAliases: await prisma.providerModelAlias.count()
49
+ };
50
+
51
+ // Check for recent updates (last 24 hours)
52
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
53
+ const recentVersions = await prisma.pricingVersion.count({
54
+ where: {
55
+ createdAt: { gte: oneDayAgo }
56
+ }
57
+ });
58
+
59
+ // Get all pricing data
60
+ const samplePricing = await prisma.pricingModelPrice.findMany({
61
+ take: 50, // Increased from 10 to show all models
62
+ orderBy: [
63
+ { provider: 'asc' },
64
+ { canonicalModel: 'asc' }
65
+ ],
66
+ select: {
67
+ provider: true,
68
+ canonicalModel: true,
69
+ unitInputPer1k: true,
70
+ unitOutputPer1k: true,
71
+ createdAt: true
72
+ }
73
+ });
74
+
75
+ return NextResponse.json({
76
+ success: true,
77
+ admin: {
78
+ userId: admin.clerkId,
79
+ role: admin.role
80
+ },
81
+ data: {
82
+ recentJobs,
83
+ activePricing: activePricing.map((v: any) => ({
84
+ id: v.id,
85
+ provider: v.provider,
86
+ isActive: v.isActive,
87
+ modelCount: v._count.models,
88
+ createdAt: v.createdAt,
89
+ note: v.note
90
+ })),
91
+ counts,
92
+ recentUpdates: {
93
+ last24Hours: recentVersions
94
+ },
95
+ samplePricing: samplePricing.map((p: any) => ({
96
+ provider: p.provider,
97
+ model: p.canonicalModel,
98
+ inputPer1k: Number(p.unitInputPer1k),
99
+ outputPer1k: Number(p.unitOutputPer1k),
100
+ createdAt: p.createdAt
101
+ }))
102
+ }
103
+ });
104
+
105
+ } catch (error) {
106
+ // Log error for debugging
107
+ // console.error('Check pricing error:', error);
108
+
109
+ if (error instanceof Error && error.message.includes('Admin access required')) {
110
+ return NextResponse.json({
111
+ error: 'Admin access required'
112
+ }, { status: 403 });
113
+ }
114
+
115
+ if (error instanceof Error && error.message.includes('Permission required')) {
116
+ return NextResponse.json({
117
+ error: 'Insufficient permissions',
118
+ message: 'You need admin privileges to view pricing data'
119
+ }, { status: 403 });
120
+ }
121
+
122
+ return NextResponse.json({
123
+ error: 'Failed to check pricing data',
124
+ details: error instanceof Error ? error.message : 'Unknown error'
125
+ }, { status: 500 });
126
+ }
127
+ }
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { auth, clerkClient } from '@clerk/nextjs/server';
3
+
4
+ // Force dynamic rendering for this route
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ export async function GET() {
8
+ try {
9
+ const { userId } = await auth();
10
+
11
+ if (!userId) {
12
+ return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
13
+ }
14
+
15
+ const adminOrgId = process.env.CLERK_ADMIN_ORGANIZATION_ID;
16
+
17
+ // Get all user's organization memberships
18
+ const client = await clerkClient();
19
+ const memberships = await client.users.getOrganizationMembershipList({
20
+ userId
21
+ });
22
+
23
+ const debug = {
24
+ userId,
25
+ adminOrgIdFromEnv: adminOrgId,
26
+ totalMemberships: memberships.data?.length || 0,
27
+ memberships: memberships.data?.map((membership: any) => ({
28
+ organizationId: membership.organization.id,
29
+ organizationName: membership.organization.name,
30
+ role: membership.role,
31
+ isAdminOrg: membership.organization.id === adminOrgId
32
+ })) || []
33
+ };
34
+
35
+ return NextResponse.json(debug);
36
+
37
+ } catch (error) {
38
+ console.error('Debug error:', error);
39
+ return NextResponse.json({
40
+ error: 'Debug failed',
41
+ details: error instanceof Error ? error.message : 'Unknown error'
42
+ }, { status: 500 });
43
+ }
44
+ }