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.
- package/.claude/settings.local.json +32 -0
- package/STRATEGIC_PLAN_2025-12-01.md +934 -0
- package/costcanary/.claude/settings.local.json +9 -0
- package/costcanary/.env.production.template +38 -0
- package/costcanary/.eslintrc.json +22 -0
- package/costcanary/.nvmrc +1 -0
- package/costcanary/.prettierignore +11 -0
- package/costcanary/.prettierrc.json +12 -0
- package/costcanary/ADMIN_SETUP.md +68 -0
- package/costcanary/CLAUDE.md +228 -0
- package/costcanary/CLERK_SETUP.md +69 -0
- package/costcanary/DATABASE_SETUP.md +136 -0
- package/costcanary/DEMO_CHECKLIST.md +62 -0
- package/costcanary/DEPLOYMENT.md +31 -0
- package/costcanary/PRODUCTION_RECOVERY.md +109 -0
- package/costcanary/README.md +247 -0
- package/costcanary/STRIPE_SECURITY_AUDIT.md +123 -0
- package/costcanary/TESTING_ADMIN.md +92 -0
- package/costcanary/app/(auth)/sign-in/[[...sign-in]]/page.tsx +25 -0
- package/costcanary/app/(auth)/sign-up/[[...sign-up]]/page.tsx +25 -0
- package/costcanary/app/(dashboard)/dashboard/admin/page.tsx +260 -0
- package/costcanary/app/(dashboard)/dashboard/alerts/page.tsx +64 -0
- package/costcanary/app/(dashboard)/dashboard/api-keys/page.tsx +231 -0
- package/costcanary/app/(dashboard)/dashboard/billing/page.tsx +349 -0
- package/costcanary/app/(dashboard)/dashboard/layout.tsx +188 -0
- package/costcanary/app/(dashboard)/dashboard/page.tsx +13 -0
- package/costcanary/app/(dashboard)/dashboard/playground/page.tsx +605 -0
- package/costcanary/app/(dashboard)/dashboard/settings/page.tsx +86 -0
- package/costcanary/app/(dashboard)/dashboard/usage/page.tsx +354 -0
- package/costcanary/app/(dashboard)/dashboard/wrapped-keys/page.tsx +677 -0
- package/costcanary/app/(marketing)/page.tsx +90 -0
- package/costcanary/app/(marketing)/pricing/page.tsx +272 -0
- package/costcanary/app/admin/pricing-status/page.tsx +338 -0
- package/costcanary/app/api/admin/check-pricing/route.ts +127 -0
- package/costcanary/app/api/admin/debug/route.ts +44 -0
- package/costcanary/app/api/admin/fix-pricing/route.ts +216 -0
- package/costcanary/app/api/admin/pricing-jobs/[jobId]/route.ts +48 -0
- package/costcanary/app/api/admin/pricing-jobs/route.ts +45 -0
- package/costcanary/app/api/admin/trigger-pricing/route.ts +209 -0
- package/costcanary/app/api/admin/whoami/route.ts +44 -0
- package/costcanary/app/api/auth/clerk/[...nextjs]/route.ts +93 -0
- package/costcanary/app/api/debug/wrapped-key/route.ts +51 -0
- package/costcanary/app/api/debug-status/route.ts +9 -0
- package/costcanary/app/api/debug-version/route.ts +12 -0
- package/costcanary/app/api/health/route.ts +14 -0
- package/costcanary/app/api/health-simple/route.ts +18 -0
- package/costcanary/app/api/keys/route.ts +162 -0
- package/costcanary/app/api/keys/wrapped/[id]/revoke/route.ts +86 -0
- package/costcanary/app/api/keys/wrapped/[id]/rotate/route.ts +81 -0
- package/costcanary/app/api/keys/wrapped/route.ts +241 -0
- package/costcanary/app/api/optimizer/preview/route.ts +147 -0
- package/costcanary/app/api/optimizer/route.ts +118 -0
- package/costcanary/app/api/pricing/models/route.ts +102 -0
- package/costcanary/app/api/proxy/[...path]/route.ts +391 -0
- package/costcanary/app/api/proxy/anthropic/route.ts +539 -0
- package/costcanary/app/api/proxy/google/route.ts +395 -0
- package/costcanary/app/api/proxy/openai/route.ts +529 -0
- package/costcanary/app/api/simple-test/route.ts +7 -0
- package/costcanary/app/api/stripe/checkout/route.ts +201 -0
- package/costcanary/app/api/stripe/webhook/route.ts +392 -0
- package/costcanary/app/api/test-connection/route.ts +209 -0
- package/costcanary/app/api/test-proxy/route.ts +7 -0
- package/costcanary/app/api/test-simple/route.ts +20 -0
- package/costcanary/app/api/usage/current/route.ts +112 -0
- package/costcanary/app/api/usage/stats/route.ts +129 -0
- package/costcanary/app/api/usage/stream/route.ts +113 -0
- package/costcanary/app/api/usage/summary/route.ts +67 -0
- package/costcanary/app/api/usage/trend/route.ts +119 -0
- package/costcanary/app/api/ws/route.ts +23 -0
- package/costcanary/app/globals.css +280 -0
- package/costcanary/app/layout.tsx +87 -0
- package/costcanary/components/Header.tsx +85 -0
- package/costcanary/components/dashboard/AddApiKeyModal.tsx +264 -0
- package/costcanary/components/dashboard/dashboard-content.tsx +329 -0
- package/costcanary/components/landing/DashboardPreview.tsx +222 -0
- package/costcanary/components/landing/Features.tsx +238 -0
- package/costcanary/components/landing/Footer.tsx +83 -0
- package/costcanary/components/landing/Hero.tsx +193 -0
- package/costcanary/components/landing/Pricing.tsx +250 -0
- package/costcanary/components/landing/Testimonials.tsx +248 -0
- package/costcanary/components/theme-provider.tsx +8 -0
- package/costcanary/components/ui/alert.tsx +59 -0
- package/costcanary/components/ui/badge.tsx +36 -0
- package/costcanary/components/ui/button.tsx +56 -0
- package/costcanary/components/ui/card.tsx +79 -0
- package/costcanary/components/ui/dialog.tsx +122 -0
- package/costcanary/components/ui/input.tsx +22 -0
- package/costcanary/components/ui/label.tsx +26 -0
- package/costcanary/components/ui/progress.tsx +28 -0
- package/costcanary/components/ui/select.tsx +160 -0
- package/costcanary/components/ui/separator.tsx +31 -0
- package/costcanary/components/ui/switch.tsx +29 -0
- package/costcanary/components/ui/tabs.tsx +55 -0
- package/costcanary/components/ui/toast.tsx +127 -0
- package/costcanary/components/ui/toaster.tsx +35 -0
- package/costcanary/components/ui/use-toast.ts +189 -0
- package/costcanary/components.json +17 -0
- package/costcanary/debug-wrapped-keys.md +117 -0
- package/costcanary/fix-console.sh +30 -0
- package/costcanary/lib/admin-auth.ts +226 -0
- package/costcanary/lib/admin-security.ts +124 -0
- package/costcanary/lib/audit-events.ts +62 -0
- package/costcanary/lib/audit.ts +158 -0
- package/costcanary/lib/chart-colors.ts +152 -0
- package/costcanary/lib/cost-calculator.ts +212 -0
- package/costcanary/lib/db-utils.ts +325 -0
- package/costcanary/lib/db.ts +14 -0
- package/costcanary/lib/encryption.ts +120 -0
- package/costcanary/lib/kms.ts +358 -0
- package/costcanary/lib/model-alias.ts +180 -0
- package/costcanary/lib/pricing.ts +292 -0
- package/costcanary/lib/prisma.ts +52 -0
- package/costcanary/lib/railway-db.ts +157 -0
- package/costcanary/lib/sse-parser.ts +283 -0
- package/costcanary/lib/stripe-client.ts +81 -0
- package/costcanary/lib/stripe-server.ts +52 -0
- package/costcanary/lib/tokens.ts +396 -0
- package/costcanary/lib/usage-limits.ts +164 -0
- package/costcanary/lib/utils.ts +6 -0
- package/costcanary/lib/websocket.ts +153 -0
- package/costcanary/lib/wrapped-keys.ts +531 -0
- package/costcanary/market-research.md +443 -0
- package/costcanary/middleware.ts +48 -0
- package/costcanary/next.config.js +43 -0
- package/costcanary/nia-sources.md +151 -0
- package/costcanary/package-lock.json +12162 -0
- package/costcanary/package.json +92 -0
- package/costcanary/package.json.backup +89 -0
- package/costcanary/postcss.config.js +6 -0
- package/costcanary/pricing-worker/.env.example +8 -0
- package/costcanary/pricing-worker/README.md +81 -0
- package/costcanary/pricing-worker/package-lock.json +1109 -0
- package/costcanary/pricing-worker/package.json +26 -0
- package/costcanary/pricing-worker/railway.json +13 -0
- package/costcanary/pricing-worker/schema.prisma +326 -0
- package/costcanary/pricing-worker/src/index.ts +115 -0
- package/costcanary/pricing-worker/src/services/pricing-updater.ts +79 -0
- package/costcanary/pricing-worker/src/services/tavily-client.ts +474 -0
- package/costcanary/pricing-worker/test-tavily.ts +47 -0
- package/costcanary/pricing-worker/tsconfig.json +24 -0
- package/costcanary/prisma/migrations/001_add_stripe_fields.sql +26 -0
- package/costcanary/prisma/schema.prisma +326 -0
- package/costcanary/prisma/seed-pricing.ts +133 -0
- package/costcanary/public/costhawk-logo.png +0 -0
- package/costcanary/railway.json +30 -0
- package/costcanary/railway.toml +16 -0
- package/costcanary/research-nia.md +298 -0
- package/costcanary/research.md +411 -0
- package/costcanary/scripts/build-production.js +65 -0
- package/costcanary/scripts/check-current-pricing.ts +51 -0
- package/costcanary/scripts/check-pricing-data.ts +174 -0
- package/costcanary/scripts/create-stripe-prices.js +49 -0
- package/costcanary/scripts/fix-pricing-data.ts +135 -0
- package/costcanary/scripts/fix-pricing-db.ts +148 -0
- package/costcanary/scripts/postinstall.js +58 -0
- package/costcanary/scripts/railway-deploy.sh +52 -0
- package/costcanary/scripts/run-migration.js +61 -0
- package/costcanary/scripts/start-production.js +175 -0
- package/costcanary/scripts/test-wrapped-key.ts +85 -0
- package/costcanary/scripts/validate-deployment.js +176 -0
- package/costcanary/scripts/validate-production.js +119 -0
- package/costcanary/server.js.backup +38 -0
- package/costcanary/tailwind.config.ts +216 -0
- package/costcanary/test-pricing-status.sh +27 -0
- package/costcanary/tsconfig.json +42 -0
- package/docs/sessions/session-2025-12-01.md +570 -0
- package/executive-summary.md +302 -0
- package/index.js +1 -0
- package/nia-sources.md +163 -0
- package/package.json +16 -0
- package/research.md +750 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
|
|
4
|
+
// Load env vars
|
|
5
|
+
config({ path: '.env.local' });
|
|
6
|
+
|
|
7
|
+
const prisma = new PrismaClient({
|
|
8
|
+
datasourceUrl: process.env.DATABASE_URL
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
async function checkPricingData() {
|
|
12
|
+
console.log('๐ Checking Pricing Data in Database\n');
|
|
13
|
+
console.log('=' .repeat(80));
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// 1. Check PricingDiscoveryJob records
|
|
17
|
+
console.log('\n๐ PRICING DISCOVERY JOBS (Last 5):');
|
|
18
|
+
console.log('-'.repeat(40));
|
|
19
|
+
|
|
20
|
+
const jobs = await prisma.pricingDiscoveryJob.findMany({
|
|
21
|
+
orderBy: { createdAt: 'desc' },
|
|
22
|
+
take: 5
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (jobs.length === 0) {
|
|
26
|
+
console.log('โ No pricing discovery jobs found');
|
|
27
|
+
} else {
|
|
28
|
+
for (const job of jobs) {
|
|
29
|
+
console.log(`\nJob ID: ${job.id}`);
|
|
30
|
+
console.log(` Status: ${job.status}`);
|
|
31
|
+
console.log(` Created: ${job.createdAt.toISOString()}`);
|
|
32
|
+
console.log(` Started: ${job.startedAt?.toISOString() || 'N/A'}`);
|
|
33
|
+
console.log(` Completed: ${job.completedAt?.toISOString() || 'N/A'}`);
|
|
34
|
+
if (job.error) {
|
|
35
|
+
console.log(` Error: ${job.error}`);
|
|
36
|
+
}
|
|
37
|
+
if (job.metadata) {
|
|
38
|
+
console.log(` Metadata: ${JSON.stringify(job.metadata, null, 2)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Check PricingVersion records
|
|
44
|
+
console.log('\n\n๐ฐ PRICING VERSIONS BY PROVIDER:');
|
|
45
|
+
console.log('-'.repeat(40));
|
|
46
|
+
|
|
47
|
+
const providers = ['OPENAI', 'ANTHROPIC', 'GOOGLE', 'GROK', 'MISTRAL', 'COHERE'];
|
|
48
|
+
|
|
49
|
+
for (const provider of providers) {
|
|
50
|
+
console.log(`\n${provider}:`);
|
|
51
|
+
|
|
52
|
+
const versions = await prisma.pricingVersion.findMany({
|
|
53
|
+
where: { provider: provider as any },
|
|
54
|
+
orderBy: { createdAt: 'desc' },
|
|
55
|
+
take: 2,
|
|
56
|
+
include: {
|
|
57
|
+
models: {
|
|
58
|
+
orderBy: { canonicalModel: 'asc' }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (versions.length === 0) {
|
|
64
|
+
console.log(' โ No pricing versions found');
|
|
65
|
+
} else {
|
|
66
|
+
for (const version of versions) {
|
|
67
|
+
console.log(` Version ID: ${version.id}`);
|
|
68
|
+
console.log(` Active: ${version.isActive ? 'โ
' : 'โ'}`);
|
|
69
|
+
console.log(` Created: ${version.createdAt.toISOString()}`);
|
|
70
|
+
console.log(` Note: ${version.note || 'N/A'}`);
|
|
71
|
+
console.log(` Models: ${version.models.length}`);
|
|
72
|
+
|
|
73
|
+
if (version.isActive && version.models.length > 0) {
|
|
74
|
+
console.log(' Sample prices:');
|
|
75
|
+
const sampleModels = version.models.slice(0, 3);
|
|
76
|
+
for (const model of sampleModels) {
|
|
77
|
+
console.log(` - ${model.canonicalModel}:`);
|
|
78
|
+
console.log(` Input: $${model.unitInputPer1k}/1K tokens`);
|
|
79
|
+
console.log(` Output: $${model.unitOutputPer1k}/1K tokens`);
|
|
80
|
+
}
|
|
81
|
+
if (version.models.length > 3) {
|
|
82
|
+
console.log(` ... and ${version.models.length - 3} more models`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3. Check Model Aliases
|
|
90
|
+
console.log('\n\n๐ MODEL ALIASES (Sample):');
|
|
91
|
+
console.log('-'.repeat(40));
|
|
92
|
+
|
|
93
|
+
const aliases = await prisma.providerModelAlias.findMany({
|
|
94
|
+
take: 10,
|
|
95
|
+
orderBy: { updatedAt: 'desc' }
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (aliases.length === 0) {
|
|
99
|
+
console.log('โ No model aliases found');
|
|
100
|
+
} else {
|
|
101
|
+
for (const alias of aliases) {
|
|
102
|
+
console.log(`${alias.provider}: ${alias.alias} โ ${alias.canonicalModel}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. Summary Statistics
|
|
107
|
+
console.log('\n\n๐ SUMMARY STATISTICS:');
|
|
108
|
+
console.log('-'.repeat(40));
|
|
109
|
+
|
|
110
|
+
const totalJobs = await prisma.pricingDiscoveryJob.count();
|
|
111
|
+
const completedJobs = await prisma.pricingDiscoveryJob.count({
|
|
112
|
+
where: { status: 'COMPLETED' }
|
|
113
|
+
});
|
|
114
|
+
const failedJobs = await prisma.pricingDiscoveryJob.count({
|
|
115
|
+
where: { status: 'FAILED' }
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log(`Total Jobs: ${totalJobs}`);
|
|
119
|
+
console.log(`Completed: ${completedJobs}`);
|
|
120
|
+
console.log(`Failed: ${failedJobs}`);
|
|
121
|
+
|
|
122
|
+
const totalVersions = await prisma.pricingVersion.count();
|
|
123
|
+
const activeVersions = await prisma.pricingVersion.count({
|
|
124
|
+
where: { isActive: true }
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log(`\nTotal Pricing Versions: ${totalVersions}`);
|
|
128
|
+
console.log(`Active Versions: ${activeVersions}`);
|
|
129
|
+
|
|
130
|
+
const totalModels = await prisma.pricingModelPrice.count();
|
|
131
|
+
console.log(`Total Model Prices: ${totalModels}`);
|
|
132
|
+
|
|
133
|
+
const totalAliases = await prisma.providerModelAlias.count();
|
|
134
|
+
console.log(`Total Model Aliases: ${totalAliases}`);
|
|
135
|
+
|
|
136
|
+
// 5. Check for recent updates
|
|
137
|
+
console.log('\n\n๐ RECENT UPDATES (Last 24 hours):');
|
|
138
|
+
console.log('-'.repeat(40));
|
|
139
|
+
|
|
140
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
141
|
+
|
|
142
|
+
const recentVersions = await prisma.pricingVersion.findMany({
|
|
143
|
+
where: {
|
|
144
|
+
createdAt: {
|
|
145
|
+
gte: oneDayAgo
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
include: {
|
|
149
|
+
models: true
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (recentVersions.length === 0) {
|
|
154
|
+
console.log('No pricing updates in the last 24 hours');
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`Found ${recentVersions.length} new pricing versions:`);
|
|
157
|
+
for (const version of recentVersions) {
|
|
158
|
+
console.log(` - ${version.provider}: ${version.models.length} models`);
|
|
159
|
+
console.log(` Created: ${version.createdAt.toISOString()}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log('\n' + '='.repeat(80));
|
|
164
|
+
console.log('โ
Pricing data check complete\n');
|
|
165
|
+
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('โ Error checking pricing data:', error);
|
|
168
|
+
} finally {
|
|
169
|
+
await prisma.$disconnect();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Run the check
|
|
174
|
+
checkPricingData().catch(console.error);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Script to create Stripe prices for existing products
|
|
2
|
+
// Run with: node scripts/create-stripe-prices.js
|
|
3
|
+
|
|
4
|
+
const Stripe = require('stripe');
|
|
5
|
+
|
|
6
|
+
// Use environment variable for security
|
|
7
|
+
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
|
|
8
|
+
if (!STRIPE_SECRET_KEY) {
|
|
9
|
+
console.error('Please set STRIPE_SECRET_KEY environment variable');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const stripe = new Stripe(STRIPE_SECRET_KEY);
|
|
13
|
+
|
|
14
|
+
async function createPrices() {
|
|
15
|
+
try {
|
|
16
|
+
// Create price for Pro plan ($9.99/month)
|
|
17
|
+
const proPrice = await stripe.prices.create({
|
|
18
|
+
product: 'prod_SrwkcBaav5iyNu',
|
|
19
|
+
unit_amount: 999, // $9.99 in cents
|
|
20
|
+
currency: 'usd',
|
|
21
|
+
recurring: {
|
|
22
|
+
interval: 'month',
|
|
23
|
+
},
|
|
24
|
+
nickname: 'Pro Monthly',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log('Pro Price created:', proPrice.id);
|
|
28
|
+
console.log('Add to Railway: STRIPE_PRO_PRICE_ID=' + proPrice.id);
|
|
29
|
+
|
|
30
|
+
// Create price for Team plan ($29.99/month)
|
|
31
|
+
const teamPrice = await stripe.prices.create({
|
|
32
|
+
product: 'prod_SrwltQwNO8UCyL',
|
|
33
|
+
unit_amount: 2999, // $29.99 in cents
|
|
34
|
+
currency: 'usd',
|
|
35
|
+
recurring: {
|
|
36
|
+
interval: 'month',
|
|
37
|
+
},
|
|
38
|
+
nickname: 'Team Monthly',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log('Team Price created:', teamPrice.id);
|
|
42
|
+
console.log('Add to Railway: STRIPE_TEAM_PRICE_ID=' + teamPrice.id);
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error creating prices:', error.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
createPrices();
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// This script will run in production via Railway
|
|
2
|
+
// Execute with: railway run npx tsx scripts/fix-pricing-data.ts
|
|
3
|
+
|
|
4
|
+
import { PrismaClient, Provider } from '@prisma/client';
|
|
5
|
+
|
|
6
|
+
const prisma = new PrismaClient();
|
|
7
|
+
|
|
8
|
+
async function fixPricingData() {
|
|
9
|
+
console.log('๐ง Fixing pricing data...\n');
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// 1. Check current state
|
|
13
|
+
const currentPricing = await prisma.pricingModelPrice.findMany({
|
|
14
|
+
where: { provider: Provider.OPENAI }
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
console.log(`Current OpenAI models: ${currentPricing.length}`);
|
|
18
|
+
currentPricing.forEach((p: any) => {
|
|
19
|
+
console.log(` - ${p.canonicalModel}: $${p.unitInputPer1k}/1k input`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 2. Deactivate all current pricing versions
|
|
23
|
+
console.log('\n๐ฆ Deactivating current pricing versions...');
|
|
24
|
+
await prisma.pricingVersion.updateMany({
|
|
25
|
+
where: { isActive: true },
|
|
26
|
+
data: { isActive: false }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 3. Delete bad pricing data (models with date names)
|
|
30
|
+
console.log('๐๏ธ Deleting bad pricing data...');
|
|
31
|
+
const badModels = await prisma.pricingModelPrice.deleteMany({
|
|
32
|
+
where: {
|
|
33
|
+
OR: [
|
|
34
|
+
{ canonicalModel: { contains: '2024' } },
|
|
35
|
+
{ canonicalModel: { contains: '2025' } },
|
|
36
|
+
{ canonicalModel: 'at' },
|
|
37
|
+
{ canonicalModel: { contains: '-09-' } },
|
|
38
|
+
{ canonicalModel: { contains: '-06-' } },
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
console.log(` Deleted ${badModels.count} bad model entries`);
|
|
43
|
+
|
|
44
|
+
// 4. Create proper OpenAI pricing
|
|
45
|
+
console.log('\nโจ Creating correct OpenAI pricing...');
|
|
46
|
+
const openaiVersion = await prisma.pricingVersion.create({
|
|
47
|
+
data: {
|
|
48
|
+
provider: Provider.OPENAI,
|
|
49
|
+
isActive: true,
|
|
50
|
+
note: 'Official OpenAI pricing - manually fixed',
|
|
51
|
+
models: {
|
|
52
|
+
create: [
|
|
53
|
+
// GPT-4o models (latest)
|
|
54
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4o', unitInputPer1k: 0.005, unitOutputPer1k: 0.015 },
|
|
55
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4o-mini', unitInputPer1k: 0.00015, unitOutputPer1k: 0.0006 },
|
|
56
|
+
|
|
57
|
+
// GPT-4 Turbo
|
|
58
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
|
|
59
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo-preview', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
|
|
60
|
+
|
|
61
|
+
// GPT-4
|
|
62
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4', unitInputPer1k: 0.03, unitOutputPer1k: 0.06 },
|
|
63
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4-32k', unitInputPer1k: 0.06, unitOutputPer1k: 0.12 },
|
|
64
|
+
|
|
65
|
+
// GPT-3.5
|
|
66
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
|
|
67
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo-16k', unitInputPer1k: 0.003, unitOutputPer1k: 0.004 },
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(`โ
Created pricing version ${openaiVersion.id} with proper model names`);
|
|
74
|
+
|
|
75
|
+
// 5. Create Anthropic pricing
|
|
76
|
+
console.log('\nโจ Creating Anthropic pricing...');
|
|
77
|
+
const anthropicVersion = await prisma.pricingVersion.create({
|
|
78
|
+
data: {
|
|
79
|
+
provider: Provider.ANTHROPIC,
|
|
80
|
+
isActive: true,
|
|
81
|
+
note: 'Official Anthropic pricing - manually added',
|
|
82
|
+
models: {
|
|
83
|
+
create: [
|
|
84
|
+
// Claude 3.5
|
|
85
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3.5-sonnet', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
86
|
+
|
|
87
|
+
// Claude 3
|
|
88
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
|
|
89
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-sonnet', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
90
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-haiku', unitInputPer1k: 0.00025, unitOutputPer1k: 0.00125 },
|
|
91
|
+
|
|
92
|
+
// Claude 2
|
|
93
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-2.1', unitInputPer1k: 0.008, unitOutputPer1k: 0.024 },
|
|
94
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-2.0', unitInputPer1k: 0.008, unitOutputPer1k: 0.024 },
|
|
95
|
+
|
|
96
|
+
// Claude Instant
|
|
97
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-instant-1.2', unitInputPer1k: 0.0008, unitOutputPer1k: 0.0024 },
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
console.log(`โ
Created Anthropic pricing version ${anthropicVersion.id}`);
|
|
104
|
+
|
|
105
|
+
// 6. Verify the fix
|
|
106
|
+
console.log('\n๐ Verifying the fix...');
|
|
107
|
+
const fixedPricing = await prisma.pricingModelPrice.findMany({
|
|
108
|
+
where: {
|
|
109
|
+
versionId: { in: [openaiVersion.id, anthropicVersion.id] }
|
|
110
|
+
},
|
|
111
|
+
orderBy: { provider: 'asc' }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(`\nFixed pricing data (${fixedPricing.length} models):`);
|
|
115
|
+
let currentProvider = '';
|
|
116
|
+
fixedPricing.forEach((p: any) => {
|
|
117
|
+
if (p.provider !== currentProvider) {
|
|
118
|
+
currentProvider = p.provider;
|
|
119
|
+
console.log(`\n${currentProvider}:`);
|
|
120
|
+
}
|
|
121
|
+
console.log(` - ${p.canonicalModel}: $${p.unitInputPer1k}/1k input, $${p.unitOutputPer1k}/1k output`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log('\nโ
Pricing data fixed successfully!');
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('โ Error fixing pricing data:', error);
|
|
128
|
+
throw error;
|
|
129
|
+
} finally {
|
|
130
|
+
await prisma.$disconnect();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Run the fix
|
|
135
|
+
fixPricingData().catch(console.error);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time script to fix pricing database with REAL API model identifiers
|
|
3
|
+
* Run with: npx tsx scripts/fix-pricing-db.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as dotenv from 'dotenv';
|
|
7
|
+
dotenv.config({ path: '.env.local' });
|
|
8
|
+
|
|
9
|
+
import { PrismaClient } from '@prisma/client';
|
|
10
|
+
|
|
11
|
+
const prisma = new PrismaClient();
|
|
12
|
+
|
|
13
|
+
enum Provider {
|
|
14
|
+
OPENAI = 'OPENAI',
|
|
15
|
+
ANTHROPIC = 'ANTHROPIC',
|
|
16
|
+
GOOGLE = 'GOOGLE',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function fixPricing() {
|
|
20
|
+
console.log('Starting pricing fix...\n');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// 1. Deactivate all current pricing versions
|
|
24
|
+
const deactivated = await prisma.pricingVersion.updateMany({
|
|
25
|
+
where: { isActive: true },
|
|
26
|
+
data: { isActive: false }
|
|
27
|
+
});
|
|
28
|
+
console.log(`Deactivated ${deactivated.count} pricing versions`);
|
|
29
|
+
|
|
30
|
+
// 2. Delete ALL existing pricing data
|
|
31
|
+
const deleted = await prisma.pricingModelPrice.deleteMany({});
|
|
32
|
+
console.log(`Deleted ${deleted.count} old pricing entries\n`);
|
|
33
|
+
|
|
34
|
+
// 3. Create OpenAI pricing with REAL API model identifiers
|
|
35
|
+
console.log('Creating OpenAI pricing...');
|
|
36
|
+
const openaiVersion = await prisma.pricingVersion.create({
|
|
37
|
+
data: {
|
|
38
|
+
provider: Provider.OPENAI,
|
|
39
|
+
isActive: true,
|
|
40
|
+
note: 'Official OpenAI pricing - December 2025 (real API model IDs)',
|
|
41
|
+
models: {
|
|
42
|
+
create: [
|
|
43
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4o', unitInputPer1k: 0.0025, unitOutputPer1k: 0.01 },
|
|
44
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4o-mini', unitInputPer1k: 0.00015, unitOutputPer1k: 0.0006 },
|
|
45
|
+
{ provider: Provider.OPENAI, canonicalModel: 'chatgpt-4o-latest', unitInputPer1k: 0.005, unitOutputPer1k: 0.015 },
|
|
46
|
+
{ provider: Provider.OPENAI, canonicalModel: 'o1', unitInputPer1k: 0.015, unitOutputPer1k: 0.06 },
|
|
47
|
+
{ provider: Provider.OPENAI, canonicalModel: 'o1-mini', unitInputPer1k: 0.003, unitOutputPer1k: 0.012 },
|
|
48
|
+
{ provider: Provider.OPENAI, canonicalModel: 'o1-preview', unitInputPer1k: 0.015, unitOutputPer1k: 0.06 },
|
|
49
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
|
|
50
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo-preview', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
|
|
51
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-4', unitInputPer1k: 0.03, unitOutputPer1k: 0.06 },
|
|
52
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
|
|
53
|
+
{ provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo-0125', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
include: { models: true }
|
|
58
|
+
});
|
|
59
|
+
console.log(` Created ${openaiVersion.models.length} OpenAI models`);
|
|
60
|
+
|
|
61
|
+
// 4. Create Anthropic pricing with all model identifiers
|
|
62
|
+
console.log('Creating Anthropic pricing...');
|
|
63
|
+
const anthropicVersion = await prisma.pricingVersion.create({
|
|
64
|
+
data: {
|
|
65
|
+
provider: Provider.ANTHROPIC,
|
|
66
|
+
isActive: true,
|
|
67
|
+
note: 'Official Anthropic pricing - December 2025 including Claude 4.5',
|
|
68
|
+
models: {
|
|
69
|
+
create: [
|
|
70
|
+
// Claude 4.5 models - FLAGSHIP (December 2025)
|
|
71
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-opus-4-5', unitInputPer1k: 0.005, unitOutputPer1k: 0.025 },
|
|
72
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-opus-4-5-20251101', unitInputPer1k: 0.005, unitOutputPer1k: 0.025 },
|
|
73
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-sonnet-4-5', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
74
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-sonnet-4-5-20250929', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
75
|
+
// Claude 3.5 Sonnet - widely used
|
|
76
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-sonnet-20241022', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
77
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-sonnet-latest', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
78
|
+
// Claude 3.5 Haiku
|
|
79
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-haiku-20241022', unitInputPer1k: 0.0008, unitOutputPer1k: 0.004 },
|
|
80
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-haiku-latest', unitInputPer1k: 0.0008, unitOutputPer1k: 0.004 },
|
|
81
|
+
// Claude 3 Opus
|
|
82
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus-20240229', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
|
|
83
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus-latest', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
|
|
84
|
+
// Claude 3 Sonnet
|
|
85
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-sonnet-20240229', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
|
|
86
|
+
// Claude 3 Haiku - fastest
|
|
87
|
+
{ provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-haiku-20240307', unitInputPer1k: 0.00025, unitOutputPer1k: 0.00125 },
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
include: { models: true }
|
|
92
|
+
});
|
|
93
|
+
console.log(` Created ${anthropicVersion.models.length} Anthropic models`);
|
|
94
|
+
|
|
95
|
+
// 5. Create Google Gemini pricing
|
|
96
|
+
console.log('Creating Google Gemini pricing...');
|
|
97
|
+
const googleVersion = await prisma.pricingVersion.create({
|
|
98
|
+
data: {
|
|
99
|
+
provider: Provider.GOOGLE,
|
|
100
|
+
isActive: true,
|
|
101
|
+
note: 'Official Google Gemini pricing - December 2025 (real API model IDs)',
|
|
102
|
+
models: {
|
|
103
|
+
create: [
|
|
104
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-2.0-flash-exp', unitInputPer1k: 0.0001, unitOutputPer1k: 0.0004 },
|
|
105
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-pro', unitInputPer1k: 0.00125, unitOutputPer1k: 0.005 },
|
|
106
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-pro-latest', unitInputPer1k: 0.00125, unitOutputPer1k: 0.005 },
|
|
107
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash', unitInputPer1k: 0.000075, unitOutputPer1k: 0.0003 },
|
|
108
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash-latest', unitInputPer1k: 0.000075, unitOutputPer1k: 0.0003 },
|
|
109
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash-8b', unitInputPer1k: 0.0000375, unitOutputPer1k: 0.00015 },
|
|
110
|
+
{ provider: Provider.GOOGLE, canonicalModel: 'gemini-pro', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
include: { models: true }
|
|
115
|
+
});
|
|
116
|
+
console.log(` Created ${googleVersion.models.length} Google models`);
|
|
117
|
+
|
|
118
|
+
// Summary
|
|
119
|
+
console.log('\n=== PRICING FIX COMPLETE ===');
|
|
120
|
+
console.log(`Total models created: ${openaiVersion.models.length + anthropicVersion.models.length + googleVersion.models.length}`);
|
|
121
|
+
console.log('\nModels by provider:');
|
|
122
|
+
|
|
123
|
+
console.log('\nOpenAI:');
|
|
124
|
+
openaiVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
|
|
125
|
+
|
|
126
|
+
console.log('\nAnthropic:');
|
|
127
|
+
anthropicVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
|
|
128
|
+
|
|
129
|
+
console.log('\nGoogle:');
|
|
130
|
+
googleVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Error fixing pricing:', error);
|
|
134
|
+
throw error;
|
|
135
|
+
} finally {
|
|
136
|
+
await prisma.$disconnect();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fixPricing()
|
|
141
|
+
.then(() => {
|
|
142
|
+
console.log('\nDone! Pricing database has been fixed with real API model identifiers.');
|
|
143
|
+
process.exit(0);
|
|
144
|
+
})
|
|
145
|
+
.catch((error) => {
|
|
146
|
+
console.error('Failed to fix pricing:', error);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Railway-Safe Postinstall Script
|
|
5
|
+
*
|
|
6
|
+
* Only generates Prisma client if we have a database URL.
|
|
7
|
+
* This prevents hanging during Railway's npm install phase.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
function log(message, type = 'info') {
|
|
13
|
+
const timestamp = new Date().toISOString();
|
|
14
|
+
const prefix = type === 'error' ? 'โ' : type === 'success' ? 'โ
' : 'โน๏ธ';
|
|
15
|
+
console.log(`${prefix} [${timestamp}] POSTINSTALL: ${message}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
log('Starting postinstall script...');
|
|
20
|
+
|
|
21
|
+
// Check if we're in Railway and have database access
|
|
22
|
+
const hasDatabase = process.env.DATABASE_URL && process.env.DATABASE_URL.length > 0;
|
|
23
|
+
const isRailway = process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_SERVICE_NAME;
|
|
24
|
+
const isNixpacksBuild = process.env.NIXPACKS_BUILD || process.env.NIXPACKS_METADATA;
|
|
25
|
+
|
|
26
|
+
// Skip Prisma generation during Railway build phase (npm install)
|
|
27
|
+
if (isRailway && isNixpacksBuild && !hasDatabase) {
|
|
28
|
+
log('Railway build phase detected - skipping Prisma generate to prevent hanging');
|
|
29
|
+
log('Prisma client will be generated during the build script instead');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (isRailway && !hasDatabase) {
|
|
34
|
+
log('Railway environment detected but no DATABASE_URL - skipping Prisma generate');
|
|
35
|
+
log('Prisma client will be generated at runtime instead');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!hasDatabase) {
|
|
40
|
+
log('No DATABASE_URL found - skipping Prisma generate (development mode)');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
log('DATABASE_URL detected - generating Prisma client...');
|
|
46
|
+
execSync('npx prisma generate', {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
cwd: process.cwd()
|
|
49
|
+
});
|
|
50
|
+
log('Prisma client generated successfully', 'success');
|
|
51
|
+
} catch (error) {
|
|
52
|
+
log(`Prisma generate failed: ${error.message}`, 'error');
|
|
53
|
+
// Don't fail the install process - we'll handle this in build
|
|
54
|
+
log('Continuing with install - will retry during build phase');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
main();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Railway Deployment Script
|
|
4
|
+
# This script runs AFTER deployment when database is accessible
|
|
5
|
+
|
|
6
|
+
set -e # Exit on any error
|
|
7
|
+
|
|
8
|
+
echo "๐ Starting Railway deployment process..."
|
|
9
|
+
|
|
10
|
+
# Check if we're in Railway environment
|
|
11
|
+
if [ -z "$RAILWAY_ENVIRONMENT" ]; then
|
|
12
|
+
echo "โ Not in Railway environment. This script is for Railway deployment only."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Check required environment variables
|
|
17
|
+
required_vars=("DATABASE_URL" "CLERK_SECRET_KEY" "ENCRYPTION_KEY")
|
|
18
|
+
for var in "${required_vars[@]}"; do
|
|
19
|
+
if [ -z "${!var}" ]; then
|
|
20
|
+
echo "โ Missing required environment variable: $var"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
echo "โ
Environment variables validated"
|
|
26
|
+
|
|
27
|
+
# Generate Prisma client (should already be done, but ensure it's there)
|
|
28
|
+
echo "๐ฆ Generating Prisma client..."
|
|
29
|
+
npx prisma generate
|
|
30
|
+
|
|
31
|
+
# Push database schema (idempotent - safe to run multiple times)
|
|
32
|
+
echo "๐๏ธ Pushing database schema..."
|
|
33
|
+
npx prisma db push --accept-data-loss
|
|
34
|
+
|
|
35
|
+
# Validate database connection
|
|
36
|
+
echo "๐ Validating database connection..."
|
|
37
|
+
npx prisma db execute --stdin <<EOF
|
|
38
|
+
SELECT 1 as test;
|
|
39
|
+
EOF
|
|
40
|
+
|
|
41
|
+
echo "โ
Database connection validated"
|
|
42
|
+
|
|
43
|
+
# Optional: Run any seeds or migrations
|
|
44
|
+
if [ "$RAILWAY_ENVIRONMENT" = "production" ]; then
|
|
45
|
+
echo "๐ฑ Running production setup..."
|
|
46
|
+
# Add any production-specific setup here
|
|
47
|
+
else
|
|
48
|
+
echo "๐งช Running non-production setup..."
|
|
49
|
+
# Add any staging/dev setup here
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "๐ Railway deployment completed successfully!"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CRITICAL PRODUCTION MIGRATION SCRIPT
|
|
5
|
+
* Run this in Railway to fix database schema mismatch
|
|
6
|
+
*
|
|
7
|
+
* Usage: node scripts/run-migration.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
console.log('๐จ CRITICAL PRODUCTION MIGRATION STARTING...');
|
|
15
|
+
console.log('This will add missing Stripe fields to the User table');
|
|
16
|
+
|
|
17
|
+
// Check if we're in production
|
|
18
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
19
|
+
console.warn('โ ๏ธ WARNING: Not running in production environment');
|
|
20
|
+
console.log('Set NODE_ENV=production if this is intentional');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Verify database connection
|
|
24
|
+
if (!process.env.DATABASE_URL) {
|
|
25
|
+
console.error('โ ERROR: DATABASE_URL not found');
|
|
26
|
+
console.error('Make sure environment variables are properly set');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
console.log('๐ Step 1: Generating Prisma client...');
|
|
32
|
+
execSync('npx prisma generate', { stdio: 'inherit' });
|
|
33
|
+
|
|
34
|
+
console.log('๐ Step 2: Checking current database schema...');
|
|
35
|
+
try {
|
|
36
|
+
execSync('npx prisma db pull --force', { stdio: 'inherit' });
|
|
37
|
+
console.log('โ
Database connection verified');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.warn('โ ๏ธ Could not pull schema, proceeding with migration...');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('๐ Step 3: Applying migration to add Stripe fields...');
|
|
43
|
+
|
|
44
|
+
// Read and execute the migration SQL
|
|
45
|
+
const migrationPath = path.join(__dirname, '../prisma/migrations/001_add_stripe_fields.sql');
|
|
46
|
+
if (!fs.existsSync(migrationPath)) {
|
|
47
|
+
console.error('โ ERROR: Migration file not found at:', migrationPath);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('๐ Step 4: Pushing schema changes to database...');
|
|
52
|
+
execSync('npx prisma db push --force-reset=false', { stdio: 'inherit' });
|
|
53
|
+
|
|
54
|
+
console.log('โ
SUCCESS: Database migration completed!');
|
|
55
|
+
console.log('๐ Next: Update Railway environment variables with production keys');
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('โ MIGRATION FAILED:', error.message);
|
|
59
|
+
console.error('Contact support immediately - database may be in inconsistent state');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|