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",
|
||||
"db:generate": "prisma generate",
|
||||
"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:studio": "prisma studio",
|
||||
"db:reset": "prisma migrate reset",
|
||||
|
@ -5,19 +5,17 @@ const prisma = new PrismaClient()
|
||||
async function main() {
|
||||
console.log('🌱 Starting database seeding...')
|
||||
|
||||
// 检查是否已存在订阅套餐
|
||||
const existingPlans = await prisma.subscriptionPlan.count()
|
||||
console.log('📦 Ensuring essential subscription plans exist...')
|
||||
|
||||
if (existingPlans > 0) {
|
||||
console.log('📦 Subscription plans already exist, skipping seed...')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('📦 Creating default subscription plans...')
|
||||
|
||||
// 创建免费套餐
|
||||
const freePlan = await prisma.subscriptionPlan.create({
|
||||
data: {
|
||||
// 确保免费套餐存在
|
||||
const freePlan = await prisma.subscriptionPlan.upsert({
|
||||
where: { id: 'free' },
|
||||
update: {
|
||||
// 更新现有套餐的关键字段
|
||||
name: 'free',
|
||||
isActive: true
|
||||
},
|
||||
create: {
|
||||
id: 'free',
|
||||
name: 'Free',
|
||||
displayName: 'Free Plan',
|
||||
@ -42,9 +40,15 @@ async function main() {
|
||||
}
|
||||
})
|
||||
|
||||
// 创建 Pro 套餐
|
||||
const proPlan = await prisma.subscriptionPlan.create({
|
||||
data: {
|
||||
// 确保 Pro 套餐存在
|
||||
const proPlan = await prisma.subscriptionPlan.upsert({
|
||||
where: { id: 'pro' },
|
||||
update: {
|
||||
// 更新现有套餐的关键字段
|
||||
name: 'pro',
|
||||
isActive: true
|
||||
},
|
||||
create: {
|
||||
id: 'pro',
|
||||
name: 'pro', // 重要:名称必须是 "pro" 用于查找
|
||||
displayName: 'Pro Plan',
|
||||
@ -88,13 +92,22 @@ async function main() {
|
||||
}
|
||||
})
|
||||
|
||||
// 过滤出需要更新的用户(没有正确的订阅套餐关联的)
|
||||
const usersToUpdate = users.filter(user =>
|
||||
!user.subscriptionPlanId ||
|
||||
user.subscriptionPlanId === '' ||
|
||||
(user.subscribePlan === 'pro' && user.subscriptionPlanId !== 'pro') ||
|
||||
(user.subscribePlan === 'free' && user.subscriptionPlanId !== 'free')
|
||||
)
|
||||
// 获取有效的套餐 ID 列表
|
||||
const validPlans = await prisma.subscriptionPlan.findMany({
|
||||
where: { isActive: true },
|
||||
select: { id: true }
|
||||
})
|
||||
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) {
|
||||
console.log(`📝 Found ${usersToUpdate.length} users to update`)
|
||||
@ -102,15 +115,16 @@ async function main() {
|
||||
for (const user of usersToUpdate) {
|
||||
let planId = 'free'
|
||||
|
||||
// 根据现有的 subscribePlan 字段确定新的套餐 ID
|
||||
if (user.subscribePlan === 'pro') {
|
||||
planId = 'pro'
|
||||
// 如果用户的 subscribePlan 是有效的,使用它;否则默认为 free
|
||||
if (validPlanIds.includes(user.subscribePlan)) {
|
||||
planId = user.subscribePlan
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
subscriptionPlanId: planId
|
||||
subscribePlan: planId, // 确保 subscribePlan 有效
|
||||
subscriptionPlanId: planId // 确保 subscriptionPlanId 有效
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user