fix seed
This commit is contained in:
parent
6cd33d24cd
commit
543a76fba4
147
docs/deployment-fix.md
Normal file
147
docs/deployment-fix.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# 部署修复说明
|
||||||
|
|
||||||
|
## 问题解决
|
||||||
|
|
||||||
|
已修复在推送到现有环境时遇到的外键约束错误:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: insert or update on table "users" violates foreign key constraint "users_subscriptionPlanId_fkey"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
通过修改种子脚本 (`prisma/seed.ts`),确保在数据库推送时:
|
||||||
|
|
||||||
|
1. **创建必需的套餐**:使用 `upsert` 确保 'free' 和 'pro' 套餐存在
|
||||||
|
2. **修复用户数据**:自动修复无效的套餐引用
|
||||||
|
3. **验证数据完整性**:确保所有用户都有有效的套餐关联
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
现在可以直接使用现有命令进行部署:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:push
|
||||||
|
```
|
||||||
|
|
||||||
|
这个命令会:
|
||||||
|
1. 推送数据库架构 (`prisma db push`)
|
||||||
|
2. 运行种子脚本 (`npm run db:seed`)
|
||||||
|
3. 自动处理数据修复
|
||||||
|
|
||||||
|
## 种子脚本改进
|
||||||
|
|
||||||
|
### 主要变更
|
||||||
|
|
||||||
|
1. **使用 upsert 而不是 create**
|
||||||
|
```typescript
|
||||||
|
// 之前:如果套餐存在就跳过
|
||||||
|
if (existingPlans > 0) return
|
||||||
|
|
||||||
|
// 现在:确保必需套餐存在
|
||||||
|
await prisma.subscriptionPlan.upsert({
|
||||||
|
where: { id: 'free' },
|
||||||
|
update: { name: 'free', isActive: true },
|
||||||
|
create: { /* 完整套餐数据 */ }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **智能用户数据修复**
|
||||||
|
```typescript
|
||||||
|
// 检查所有用户的套餐引用
|
||||||
|
const validPlanIds = ['free', 'pro']
|
||||||
|
const usersToUpdate = users.filter(user => {
|
||||||
|
const hasValidSubscribePlan = validPlanIds.includes(user.subscribePlan)
|
||||||
|
const hasValidSubscriptionPlanId = validPlanIds.includes(user.subscriptionPlanId)
|
||||||
|
return !hasValidSubscribePlan || !hasValidSubscriptionPlanId
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修复无效引用
|
||||||
|
for (const user of usersToUpdate) {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
subscribePlan: validPlanIds.includes(user.subscribePlan) ? user.subscribePlan : 'free',
|
||||||
|
subscriptionPlanId: validPlanIds.includes(user.subscribePlan) ? user.subscribePlan : 'free'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 执行流程
|
||||||
|
|
||||||
|
1. **创建/更新套餐**:确保 'free' 和 'pro' 套餐存在
|
||||||
|
2. **检查用户数据**:找出有无效套餐引用的用户
|
||||||
|
3. **修复用户数据**:将无效引用改为 'free'
|
||||||
|
4. **验证结果**:确认所有数据都是一致的
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
种子脚本运行结果:
|
||||||
|
```
|
||||||
|
🌱 Starting database seeding...
|
||||||
|
📦 Ensuring essential subscription plans exist...
|
||||||
|
✅ Created subscription plan: Free Plan
|
||||||
|
✅ Created subscription plan: Pro Plan
|
||||||
|
👥 Updating existing users...
|
||||||
|
👥 No users need subscription plan updates
|
||||||
|
📊 Seeding completed:
|
||||||
|
• 2 subscription plans created
|
||||||
|
• 2 users have valid plan associations
|
||||||
|
🎉 Database seeding finished successfully!
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署到现有环境
|
||||||
|
|
||||||
|
现在可以安全地部署到任何现有环境:
|
||||||
|
|
||||||
|
1. **本地测试**:
|
||||||
|
```bash
|
||||||
|
npm run db:push
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **生产部署**:
|
||||||
|
```bash
|
||||||
|
npm run db:push
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## 优势
|
||||||
|
|
||||||
|
1. **无需额外脚本**:使用现有的 `npm run db:push` 命令
|
||||||
|
2. **自动修复**:种子脚本自动处理数据不一致问题
|
||||||
|
3. **向后兼容**:不会破坏现有数据
|
||||||
|
4. **幂等操作**:可以重复运行而不会出错
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **Stripe 配置**:部署后需要设置 Pro 套餐的 Stripe 价格 ID
|
||||||
|
```sql
|
||||||
|
UPDATE subscription_plans
|
||||||
|
SET "stripePriceId" = 'price_your_stripe_price_id'
|
||||||
|
WHERE name = 'pro';
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **数据验证**:部署后验证定价页面和订阅功能
|
||||||
|
3. **备份建议**:在生产环境部署前建议备份数据库
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
如果仍然遇到问题:
|
||||||
|
|
||||||
|
1. **检查套餐**:
|
||||||
|
```sql
|
||||||
|
SELECT id, name, "isActive" FROM subscription_plans;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **检查用户**:
|
||||||
|
```sql
|
||||||
|
SELECT "subscribePlan", COUNT(*) FROM users GROUP BY "subscribePlan";
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **手动修复**:
|
||||||
|
```sql
|
||||||
|
UPDATE users SET "subscribePlan" = 'free'
|
||||||
|
WHERE "subscribePlan" NOT IN ('free', 'pro');
|
||||||
|
```
|
@ -13,6 +13,8 @@
|
|||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"db:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:push": "prisma db push && npm run db:seed",
|
"db:push": "prisma db push && npm run db:seed",
|
||||||
|
"db:deploy": "./scripts/deploy-with-migration.sh",
|
||||||
|
"db:safe-migrate": "tsx scripts/safe-db-migration.ts",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"db:reset": "prisma migrate reset",
|
"db:reset": "prisma migrate reset",
|
||||||
|
@ -5,19 +5,17 @@ const prisma = new PrismaClient()
|
|||||||
async function main() {
|
async function main() {
|
||||||
console.log('🌱 Starting database seeding...')
|
console.log('🌱 Starting database seeding...')
|
||||||
|
|
||||||
// 检查是否已存在订阅套餐
|
console.log('📦 Ensuring essential subscription plans exist...')
|
||||||
const existingPlans = await prisma.subscriptionPlan.count()
|
|
||||||
|
|
||||||
if (existingPlans > 0) {
|
|
||||||
console.log('📦 Subscription plans already exist, skipping seed...')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('📦 Creating default subscription plans...')
|
// 确保免费套餐存在
|
||||||
|
const freePlan = await prisma.subscriptionPlan.upsert({
|
||||||
// 创建免费套餐
|
where: { id: 'free' },
|
||||||
const freePlan = await prisma.subscriptionPlan.create({
|
update: {
|
||||||
data: {
|
// 更新现有套餐的关键字段
|
||||||
|
name: 'free',
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
id: 'free',
|
id: 'free',
|
||||||
name: 'Free',
|
name: 'Free',
|
||||||
displayName: 'Free Plan',
|
displayName: 'Free Plan',
|
||||||
@ -42,9 +40,15 @@ async function main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建 Pro 套餐
|
// 确保 Pro 套餐存在
|
||||||
const proPlan = await prisma.subscriptionPlan.create({
|
const proPlan = await prisma.subscriptionPlan.upsert({
|
||||||
data: {
|
where: { id: 'pro' },
|
||||||
|
update: {
|
||||||
|
// 更新现有套餐的关键字段
|
||||||
|
name: 'pro',
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
id: 'pro',
|
id: 'pro',
|
||||||
name: 'pro', // 重要:名称必须是 "pro" 用于查找
|
name: 'pro', // 重要:名称必须是 "pro" 用于查找
|
||||||
displayName: 'Pro Plan',
|
displayName: 'Pro Plan',
|
||||||
@ -88,13 +92,22 @@ async function main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 过滤出需要更新的用户(没有正确的订阅套餐关联的)
|
// 获取有效的套餐 ID 列表
|
||||||
const usersToUpdate = users.filter(user =>
|
const validPlans = await prisma.subscriptionPlan.findMany({
|
||||||
!user.subscriptionPlanId ||
|
where: { isActive: true },
|
||||||
user.subscriptionPlanId === '' ||
|
select: { id: true }
|
||||||
(user.subscribePlan === 'pro' && user.subscriptionPlanId !== 'pro') ||
|
})
|
||||||
(user.subscribePlan === 'free' && user.subscriptionPlanId !== 'free')
|
const validPlanIds = validPlans.map(plan => plan.id)
|
||||||
)
|
|
||||||
|
// 过滤出需要更新的用户
|
||||||
|
const usersToUpdate = users.filter(user => {
|
||||||
|
// 检查 subscribePlan 是否有效
|
||||||
|
const hasValidSubscribePlan = validPlanIds.includes(user.subscribePlan)
|
||||||
|
// 检查 subscriptionPlanId 是否正确
|
||||||
|
const hasValidSubscriptionPlanId = user.subscriptionPlanId && validPlanIds.includes(user.subscriptionPlanId)
|
||||||
|
|
||||||
|
return !hasValidSubscribePlan || !hasValidSubscriptionPlanId
|
||||||
|
})
|
||||||
|
|
||||||
if (usersToUpdate.length > 0) {
|
if (usersToUpdate.length > 0) {
|
||||||
console.log(`📝 Found ${usersToUpdate.length} users to update`)
|
console.log(`📝 Found ${usersToUpdate.length} users to update`)
|
||||||
@ -102,15 +115,16 @@ async function main() {
|
|||||||
for (const user of usersToUpdate) {
|
for (const user of usersToUpdate) {
|
||||||
let planId = 'free'
|
let planId = 'free'
|
||||||
|
|
||||||
// 根据现有的 subscribePlan 字段确定新的套餐 ID
|
// 如果用户的 subscribePlan 是有效的,使用它;否则默认为 free
|
||||||
if (user.subscribePlan === 'pro') {
|
if (validPlanIds.includes(user.subscribePlan)) {
|
||||||
planId = 'pro'
|
planId = user.subscribePlan
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
data: {
|
data: {
|
||||||
subscriptionPlanId: planId
|
subscribePlan: planId, // 确保 subscribePlan 有效
|
||||||
|
subscriptionPlanId: planId // 确保 subscriptionPlanId 有效
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user