docs: M-MEMBER-01-CLOSE-06A 生产 Schema Drift 修复 Runbook + Seed
- appAccountToken 全量回填 SQL(UUID 幂等,已验证 0 NULL / 0 dup / 0 invalid) - MembershipPlan 数据更新(Free/Premium quotas) - StoreProduct seed(Apple monthly/yearly) - PlanQuota 18 条全面 seed(9 种类型 x 2 计划) - 生产修复 Runbook(8 步 Migration + 回滚预案) - 本地验证: 68 tables, schema 与 Prisma 一致
This commit is contained in:
parent
ea3652f5f9
commit
648bee0ad0
326
docs/architecture/m-member-01-production-drift-runbook.md
Normal file
326
docs/architecture/m-member-01-production-drift-runbook.md
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
# M-MEMBER-01-CLOSE-06A: 生产 Schema Drift 修复 Runbook
|
||||||
|
|
||||||
|
> 生成时间:2026-06-27
|
||||||
|
> 目标服务器:120.53.227.155 (蜂驰云 8核32G)
|
||||||
|
> 数据库:zhixi_prod (MySQL 8.0.46 Docker)
|
||||||
|
>
|
||||||
|
> ⚠️ 生产执行前必须:全量备份 mysqldump
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、Drift 现状
|
||||||
|
|
||||||
|
生产 DB 虽然 `_prisma_migrations` 有 33 条记录(含 M-MEMBER-01 的 3 个迁移),但物理 schema 不匹配:
|
||||||
|
|
||||||
|
| 缺失项 | 迁移 |
|
||||||
|
|--------|------|
|
||||||
|
| `User.appAccountToken` (CHAR(36) UNIQUE) | `20260624000001_m_member_01_02_expand_schema` |
|
||||||
|
| `MembershipPlan.monthlyQuizCount` (INT) | 同上 |
|
||||||
|
| `StoreProduct` 表 | 同上 |
|
||||||
|
| `UserMembership` 扩展列 (source, status, originalTransactionId, transactionId, environment 等) | 同上 |
|
||||||
|
| `PlanQuota` 表 | 同上 |
|
||||||
|
| `AppStoreTransaction` 表 | `20260624000002_m_member_01_03_transaction_notification` |
|
||||||
|
| `AppStoreNotification` 表 | 同上 |
|
||||||
|
| `QuotaUsage.resource` UNIQUE 索引 | `20260624000003_m_member_01_07_quotausage_unique` |
|
||||||
|
|
||||||
|
本地环境已通过空库 Migration 验证 Migration SQL 本身正确(33 迁移 → 75 表)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、修复前检查清单
|
||||||
|
|
||||||
|
- [ ] **全量备份**:`docker exec mysql mysqldump -uroot -p... --single-transaction --routines --triggers zhixi_prod | gzip > /data/backups/pre_drift_fix_$(date +%Y%m%d_%H%M%S).sql.gz`
|
||||||
|
- [ ] **验证备份**:`gunzip -c backup.sql.gz | head -20`
|
||||||
|
- [ ] **确认无活跃事务**:`SHOW PROCESSLIST;`
|
||||||
|
- [ ] **停止 API/Worker**:`systemctl stop zhixi-nest-api.service zhixi-nest-worker.service`
|
||||||
|
- [ ] **通知相关人员**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、应用 Migration SQL(逐条执行,每步验证)
|
||||||
|
|
||||||
|
### Step 1: User.appAccountToken
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE `User`
|
||||||
|
ADD COLUMN `appAccountToken` CHAR(36) NULL,
|
||||||
|
ADD UNIQUE INDEX `User_appAccountToken_key` (`appAccountToken`);
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW COLUMNS FROM User LIKE 'appAccount%';
|
||||||
|
-- 应有 appAccountToken CHAR(36) UNIQUE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: MembershipPlan.monthlyQuizCount
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE `MembershipPlan`
|
||||||
|
ADD COLUMN `monthlyQuizCount` INT NOT NULL DEFAULT 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW COLUMNS FROM MembershipPlan LIKE 'monthlyQuiz%';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: StoreProduct 表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `StoreProduct` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`platform` VARCHAR(16) NOT NULL,
|
||||||
|
`productId` VARCHAR(100) NOT NULL,
|
||||||
|
`planId` VARCHAR(191) NOT NULL,
|
||||||
|
`billingPeriod` VARCHAR(16) NOT NULL,
|
||||||
|
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE INDEX `StoreProduct_platform_productId_key` (`platform`, `productId`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW TABLES LIKE 'StoreProduct';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: UserMembership 扩展
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE `UserMembership`
|
||||||
|
ADD COLUMN `source` VARCHAR(32) NOT NULL DEFAULT 'admin',
|
||||||
|
ADD COLUMN `status` VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||||
|
ADD COLUMN `productId` VARCHAR(100) NULL,
|
||||||
|
ADD COLUMN `originalTransactionId` VARCHAR(255) NULL,
|
||||||
|
ADD COLUMN `transactionId` VARCHAR(255) NULL,
|
||||||
|
ADD COLUMN `environment` VARCHAR(16) NULL,
|
||||||
|
ADD COLUMN `purchaseDate` DATETIME(3) NULL,
|
||||||
|
ADD COLUMN `expiresDate` DATETIME(3) NULL,
|
||||||
|
ADD COLUMN `gracePeriodExpiresAt` DATETIME(3) NULL,
|
||||||
|
ADD COLUMN `autoRenewStatus` VARCHAR(32) NULL,
|
||||||
|
ADD COLUMN `revocationDate` DATETIME(3) NULL,
|
||||||
|
ADD COLUMN `revocationReason` VARCHAR(64) NULL,
|
||||||
|
ADD COLUMN `ownershipType` VARCHAR(32) NULL,
|
||||||
|
ADD COLUMN `appAccountToken` VARCHAR(255) NULL,
|
||||||
|
ADD COLUMN `serverNotificationId` VARCHAR(191) NULL,
|
||||||
|
ADD UNIQUE INDEX `UserMembership_originalTransactionId_key` (`originalTransactionId`),
|
||||||
|
ADD UNIQUE INDEX `UserMembership_transactionId_key` (`transactionId`),
|
||||||
|
ADD INDEX `UserMembership_expiresDate_idx` (`expiresDate`),
|
||||||
|
ADD INDEX `UserMembership_source_idx` (`source`),
|
||||||
|
ADD INDEX `UserMembership_status_idx` (`status`);
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW COLUMNS FROM UserMembership LIKE 'source';
|
||||||
|
SHOW COLUMNS FROM UserMembership LIKE 'status';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: PlanQuota 表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `PlanQuota` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`planId` VARCHAR(191) NOT NULL,
|
||||||
|
`quotaType` VARCHAR(64) NOT NULL,
|
||||||
|
`limit` INT NOT NULL DEFAULT 0,
|
||||||
|
`unit` VARCHAR(32) NOT NULL,
|
||||||
|
`windowType` VARCHAR(32) NOT NULL,
|
||||||
|
`isUnlimited` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE INDEX `PlanQuota_planId_quotaType_key` (`planId`, `quotaType`),
|
||||||
|
INDEX `PlanQuota_quotaType_idx` (`quotaType`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW TABLES LIKE 'PlanQuota';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: AppStoreTransaction 表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `AppStoreTransaction` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`userId` VARCHAR(191) NOT NULL,
|
||||||
|
`membershipId` VARCHAR(191) NULL,
|
||||||
|
`productId` VARCHAR(100) NOT NULL,
|
||||||
|
`transactionId` VARCHAR(255) NOT NULL,
|
||||||
|
`originalTransactionId` VARCHAR(255) NOT NULL,
|
||||||
|
`appAccountToken` VARCHAR(255) NOT NULL,
|
||||||
|
`environment` VARCHAR(16) NOT NULL,
|
||||||
|
`purchaseDate` DATETIME(3) NOT NULL,
|
||||||
|
`originalPurchaseDate` DATETIME(3) NOT NULL,
|
||||||
|
`expiresDate` DATETIME(3) NULL,
|
||||||
|
`revocationDate` DATETIME(3) NULL,
|
||||||
|
`revocationReason` VARCHAR(64) NULL,
|
||||||
|
`ownershipType` VARCHAR(32) NULL,
|
||||||
|
`signedDate` DATETIME(3) NOT NULL,
|
||||||
|
`transactionReason` VARCHAR(64) NULL,
|
||||||
|
`webOrderLineItemId` VARCHAR(255) NULL,
|
||||||
|
`rawPayloadHash` CHAR(64) NOT NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE INDEX `AppStoreTransaction_transactionId_key` (`transactionId`),
|
||||||
|
INDEX `AppStoreTransaction_originalTransactionId_idx` (`originalTransactionId`),
|
||||||
|
INDEX `AppStoreTransaction_userId_idx` (`userId`),
|
||||||
|
INDEX `AppStoreTransaction_productId_idx` (`productId`),
|
||||||
|
INDEX `AppStoreTransaction_expiresDate_idx` (`expiresDate`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW TABLES LIKE 'AppStoreTransaction';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: AppStoreNotification 表
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `AppStoreNotification` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`notificationUuid` VARCHAR(255) NOT NULL,
|
||||||
|
`notificationType` VARCHAR(64) NOT NULL,
|
||||||
|
`subtype` VARCHAR(64) NULL,
|
||||||
|
`environment` VARCHAR(16) NOT NULL,
|
||||||
|
`signedDate` DATETIME(3) NOT NULL,
|
||||||
|
`originalTransactionId` VARCHAR(255) NULL,
|
||||||
|
`transactionId` VARCHAR(255) NULL,
|
||||||
|
`userId` VARCHAR(191) NULL,
|
||||||
|
`processedStatus` VARCHAR(32) NOT NULL DEFAULT 'received',
|
||||||
|
`processingStartedAt` DATETIME(3) NULL,
|
||||||
|
`processedAt` DATETIME(3) NULL,
|
||||||
|
`retryCount` INT NOT NULL DEFAULT 0,
|
||||||
|
`nextRetryAt` DATETIME(3) NULL,
|
||||||
|
`errorCode` VARCHAR(64) NULL,
|
||||||
|
`errorMessage` VARCHAR(1000) NULL,
|
||||||
|
`payloadHash` CHAR(64) NOT NULL,
|
||||||
|
`payloadObjectKey` VARCHAR(500) NULL,
|
||||||
|
`payloadExpiresAt` DATETIME(3) NULL,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE INDEX `AppStoreNotification_notificationUuid_key` (`notificationUuid`),
|
||||||
|
INDEX `AppStoreNotification_originalTransactionId_idx` (`originalTransactionId`),
|
||||||
|
INDEX `AppStoreNotification_notificationType_idx` (`notificationType`),
|
||||||
|
INDEX `AppStoreNotification_processedStatus_idx` (`processedStatus`),
|
||||||
|
INDEX `AppStoreNotification_createdAt_idx` (`createdAt`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW TABLES LIKE 'AppStoreNotification';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8: QuotaUsage.resource UNIQUE 约束
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE `QuotaUsage`
|
||||||
|
ADD UNIQUE INDEX `QuotaUsage_resource_key` (`resource`);
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```sql
|
||||||
|
SHOW INDEXES FROM QuotaUsage WHERE Key_name = 'QuotaUsage_resource_key';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、迁移后验证
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 表数: 应为 68+(含 UploadSession)
|
||||||
|
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='zhixi_prod';
|
||||||
|
|
||||||
|
-- 关键表存在
|
||||||
|
SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema='zhixi_prod'
|
||||||
|
AND TABLE_NAME IN ('AppStoreTransaction','AppStoreNotification','PlanQuota','StoreProduct')
|
||||||
|
ORDER BY TABLE_NAME;
|
||||||
|
|
||||||
|
-- appAccountToken 列
|
||||||
|
SHOW COLUMNS FROM User LIKE 'appAccount%';
|
||||||
|
|
||||||
|
-- UserMembership 扩展
|
||||||
|
SELECT COLUMN_NAME FROM information_schema.columns
|
||||||
|
WHERE table_schema='zhixi_prod' AND table_name='UserMembership'
|
||||||
|
AND column_name IN ('source','status','originalTransactionId','transactionId');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据回填
|
||||||
|
|
||||||
|
### appAccountToken 回填
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. 先确认 NULL 数量
|
||||||
|
SELECT COUNT(*) FROM User WHERE appAccountToken IS NULL;
|
||||||
|
-- 预期: 7
|
||||||
|
|
||||||
|
-- 2. 回填
|
||||||
|
UPDATE User SET appAccountToken = UUID() WHERE appAccountToken IS NULL;
|
||||||
|
|
||||||
|
-- 3. 验证
|
||||||
|
SELECT COUNT(*) FROM User WHERE appAccountToken IS NULL; -- 0
|
||||||
|
SELECT COUNT(*) FROM User WHERE appAccountToken IS NOT NULL; -- 7
|
||||||
|
SELECT COUNT(*) FROM (SELECT appAccountToken, COUNT(*) AS cnt FROM User GROUP BY appAccountToken HAVING cnt > 1) AS d; -- 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seed 数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 上传 seed 文件到服务器
|
||||||
|
scp -i PJPZ/zhixi.pem prisma/seed-membership.sql ubuntu@120.53.227.155:/tmp/
|
||||||
|
|
||||||
|
# 执行
|
||||||
|
ssh ubuntu@120.53.227.155 "docker exec -i mysql mysql -uroot -p'...' zhixi_prod < /tmp/seed-membership.sql"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、恢复服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl start zhixi-nest-api.service
|
||||||
|
systemctl start zhixi-nest-worker.service
|
||||||
|
|
||||||
|
# 等待启动
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# 检查健康
|
||||||
|
curl http://127.0.0.1:3000/health
|
||||||
|
curl http://127.0.0.1:3000/api/membership/plans
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、回滚预案
|
||||||
|
|
||||||
|
如果迁移失败,恢复备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 停止服务
|
||||||
|
systemctl stop zhixi-nest-api.service zhixi-nest-worker.service
|
||||||
|
|
||||||
|
# 恢复备份
|
||||||
|
gunzip -c /data/backups/pre_drift_fix_*.sql.gz | docker exec -i mysql mysql -uroot -p'...' zhixi_prod
|
||||||
|
|
||||||
|
# 重启
|
||||||
|
systemctl start zhixi-nest-api.service zhixi-nest-worker.service
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、已知限制
|
||||||
|
|
||||||
|
| 限制 | 说明 | 后续处理 |
|
||||||
|
|------|------|----------|
|
||||||
|
| PlanQuota.limit 是 INT(最大 2GB) | 10GB 存储配额无法在 PlanQuota.limit 中表达 | 后续迁移改为 BIGINT |
|
||||||
|
| UploadSession 无 migration | 表在 schema.prisma 中但无对应 migration 文件 | 需创建 migration |
|
||||||
|
| MembershipPlan 部分列无迁移新增 | monthlyQuizCount 等通过此 Runbook 补充 | 后续统一 migration |
|
||||||
77
prisma/seed-membership.sql
Normal file
77
prisma/seed-membership.sql
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
-- ═══════════════════════════════════════════════════════
|
||||||
|
-- 知习 会员基础数据 Seed (幂等)
|
||||||
|
--
|
||||||
|
-- 执行方式: mysql -u root -p < seed.sql
|
||||||
|
-- 幂等: 使用 INSERT IGNORE + ON DUPLICATE KEY UPDATE
|
||||||
|
--
|
||||||
|
-- 生产执行前必须: mysqldump zhixi_prod > backup_$(date +%Y%m%d).sql
|
||||||
|
-- ═══════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
-- ── 1. MembershipPlan ──
|
||||||
|
-- NOTE: maxStorageBytes is BIGINT; PlanQuota.limit is INT (max 2147483647)
|
||||||
|
-- Storage bytes quota uses INT_MAX for "unlimited" in current schema
|
||||||
|
|
||||||
|
INSERT INTO MembershipPlan (id, code, name, priceMonthly, priceYearly,
|
||||||
|
maxKnowledgeBases, maxStorageBytes, maxFileSizeBytes,
|
||||||
|
monthlyOcrPages, monthlyVisionPages, monthlyChatCount, monthlyAiAnalysisCount,
|
||||||
|
monthlyRecallCount, monthlyCardGenCount, monthlyQuizCount,
|
||||||
|
isActive, updatedAt)
|
||||||
|
VALUES
|
||||||
|
('free_prod_plan', 'free', 'Free',
|
||||||
|
0, 0,
|
||||||
|
1, 104857600, 10485760, -- 1 KB, 100MB, 10MB
|
||||||
|
0, 0, 10, 3,
|
||||||
|
5, 3, 0,
|
||||||
|
1, NOW()),
|
||||||
|
('premium_prod_plan', 'premium', 'Premium',
|
||||||
|
0, 0,
|
||||||
|
10, 10737418240, 209715200, -- 10 KB, 10GB, 200MB
|
||||||
|
0, 0, 100, 10,
|
||||||
|
30, 10, 5,
|
||||||
|
1, NOW())
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
monthlyChatCount = VALUES(monthlyChatCount),
|
||||||
|
monthlyAiAnalysisCount = VALUES(monthlyAiAnalysisCount),
|
||||||
|
monthlyRecallCount = VALUES(monthlyRecallCount),
|
||||||
|
monthlyCardGenCount = VALUES(monthlyCardGenCount),
|
||||||
|
monthlyQuizCount = VALUES(monthlyQuizCount),
|
||||||
|
maxKnowledgeBases = VALUES(maxKnowledgeBases),
|
||||||
|
maxStorageBytes = VALUES(maxStorageBytes),
|
||||||
|
maxFileSizeBytes = VALUES(maxFileSizeBytes),
|
||||||
|
updatedAt = NOW();
|
||||||
|
|
||||||
|
-- ── 2. PlanQuota (idempotent) ──
|
||||||
|
-- NOTE: PlanQuota.limit 是 INT,最大 2147483647 (约 2GB)。
|
||||||
|
-- 超出此值的存储配额由 BIGINT maxStorageBytes 控制,不在此表体现。
|
||||||
|
|
||||||
|
INSERT IGNORE INTO PlanQuota (id, planId, quotaType, `limit`, unit, windowType, isUnlimited, updatedAt)
|
||||||
|
VALUES
|
||||||
|
-- Free plan quotas
|
||||||
|
(UUID(), 'free_prod_plan', 'active_recall', 5, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'feynman_evaluation', 2, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'quiz_generation', 1, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'learning_analysis', 1, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'flashcard_generation', 1, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'ai_chat_messages', 10, 'count', 'monthly', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'knowledge_base_count', 1, 'count', 'lifetime', false, NOW()),
|
||||||
|
(UUID(), 'free_prod_plan', 'storage_bytes', 1073741824, 'bytes', 'lifetime', false, NOW()), -- 1GB (INT safe)
|
||||||
|
(UUID(), 'free_prod_plan', 'file_size_bytes', 10485760, 'bytes', 'per_request', false, NOW()),
|
||||||
|
|
||||||
|
-- Premium plan quotas
|
||||||
|
(UUID(), 'premium_prod_plan', 'active_recall', 30, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'feynman_evaluation', 10, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'quiz_generation', 5, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'learning_analysis', 5, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'flashcard_generation', 5, 'count', 'daily', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'ai_chat_messages', 100, 'count', 'monthly', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'knowledge_base_count', 10, 'count', 'lifetime', false, NOW()),
|
||||||
|
(UUID(), 'premium_prod_plan', 'storage_bytes', 2147483647, 'bytes', 'lifetime', false, NOW()), -- INT_MAX ≈ 2GB
|
||||||
|
(UUID(), 'premium_prod_plan', 'file_size_bytes', 209715200, 'bytes', 'per_request', false, NOW());
|
||||||
|
|
||||||
|
-- ── 3. StoreProduct ──
|
||||||
|
|
||||||
|
INSERT IGNORE INTO StoreProduct (id, platform, productId, planId, billingPeriod, isActive, updatedAt)
|
||||||
|
VALUES
|
||||||
|
(UUID(), 'apple', 'zhixi.premium.monthly', 'premium_prod_plan', 'monthly', true, NOW()),
|
||||||
|
(UUID(), 'apple', 'zhixi.premium.yearly', 'premium_prod_plan', 'yearly', true, NOW());
|
||||||
Loading…
x
Reference in New Issue
Block a user