feat(server): pro 套餐设备上限 5 → 3
- seed(sqlite/mysql 000007)+ 001_init:pro max_devices 5→3(全新库直接对) - 新增迁移 000019(sqlite/mysql,up/down):UPDATE plans SET max_devices=3 WHERE code='pro' —— 已迁移的线上库靠它更新;down 回退 5 - 000002/001 注释同步(free 1 / pro 3 / team 10) - 测试:sqlite_migrate 期望版本 18→19;devices 集成测试 pro 断言 5→3 - free(1)/team(10)不变 注:设备上限当前仍未强制执行(登录注册 MaxDevices=0),仅数据/展示口径; 真正启用+超限 UX 见 todo #16。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -172,15 +172,15 @@ func TestFullChain(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
userID := createUser(t, db, "chain@example.com", "active")
|
userID := createUser(t, db, "chain@example.com", "active")
|
||||||
// 7-day pro trial → effective plan pro (max 5).
|
// 7-day pro trial → effective plan pro (max 3).
|
||||||
giveSubscription(t, db, userID, "pro", "trial", time.Now().UTC().Add(7*24*time.Hour))
|
giveSubscription(t, db, userID, "pro", "trial", time.Now().UTC().Add(7*24*time.Hour))
|
||||||
|
|
||||||
plan, apiErr := svc.ResolvePlan(ctx, userID)
|
plan, apiErr := svc.ResolvePlan(ctx, userID)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
t.Fatalf("ResolvePlan: %v", apiErr)
|
t.Fatalf("ResolvePlan: %v", apiErr)
|
||||||
}
|
}
|
||||||
if plan.PlanCode != "pro" || plan.MaxDevices != 5 {
|
if plan.PlanCode != "pro" || plan.MaxDevices != 3 {
|
||||||
t.Fatalf("want pro/5, got %s/%d", plan.PlanCode, plan.MaxDevices)
|
t.Fatalf("want pro/3, got %s/%d", plan.PlanCode, plan.MaxDevices)
|
||||||
}
|
}
|
||||||
|
|
||||||
devUUID := newUUID(t, db)
|
devUUID := newUUID(t, db)
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ func TestSQLiteMigrateUpDown(t *testing.T) {
|
|||||||
if dirty {
|
if dirty {
|
||||||
t.Fatalf("schema dirty after MigrateUp")
|
t.Fatalf("schema dirty after MigrateUp")
|
||||||
}
|
}
|
||||||
if v != 18 {
|
if v != 19 {
|
||||||
t.Errorf("version = %d, want 18", v)
|
t.Errorf("version = %d, want 19", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Core tables exist.
|
// 2. Core tables exist.
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
CREATE TABLE IF NOT EXISTS plans (
|
CREATE TABLE IF NOT EXISTS plans (
|
||||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
code ENUM('free','pro','team') NOT NULL UNIQUE,
|
code ENUM('free','pro','team') NOT NULL UNIQUE,
|
||||||
max_devices INT NOT NULL, -- free 1 / pro 5 / team 10
|
max_devices INT NOT NULL, -- free 1 / pro 3 / team 10
|
||||||
daily_minutes INT NULL, -- free 10; NULL = unlimited
|
daily_minutes INT NULL, -- free 10; NULL = unlimited
|
||||||
ad_gate BOOLEAN NOT NULL DEFAULT FALSE -- free users must watch an ad daily
|
ad_gate BOOLEAN NOT NULL DEFAULT FALSE -- free users must watch an ad daily
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
INSERT IGNORE INTO plans (code, max_devices, daily_minutes, ad_gate) VALUES
|
INSERT IGNORE INTO plans (code, max_devices, daily_minutes, ad_gate) VALUES
|
||||||
('free', 1, 10, TRUE),
|
('free', 1, 10, TRUE),
|
||||||
('pro', 5, NULL, FALSE),
|
('pro', 3, NULL, FALSE),
|
||||||
('team', 10, NULL, FALSE);
|
('team', 10, NULL, FALSE);
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
CREATE TABLE plans (
|
CREATE TABLE plans (
|
||||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
code ENUM('free','pro','team') NOT NULL UNIQUE,
|
code ENUM('free','pro','team') NOT NULL UNIQUE,
|
||||||
max_devices INT NOT NULL, -- free 1 / pro 5 / team 10
|
max_devices INT NOT NULL, -- free 1 / pro 3 / team 10
|
||||||
daily_minutes INT NULL, -- free 10;NULL = 不限
|
daily_minutes INT NULL, -- free 10;NULL = 不限
|
||||||
ad_gate BOOLEAN NOT NULL DEFAULT FALSE -- free 每日看广告解锁
|
ad_gate BOOLEAN NOT NULL DEFAULT FALSE -- free 每日看广告解锁
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
-- plans seed(口径来源:design/CLAUDE.md §7,价格不入表)
|
-- plans seed(口径来源:design/CLAUDE.md §7,价格不入表)
|
||||||
-- free: 1 设备,10 分钟/天,每日广告解锁
|
-- free: 1 设备,10 分钟/天,每日广告解锁
|
||||||
-- pro: 5 设备,不限时长,无广告门槛
|
-- pro: 3 设备,不限时长,无广告门槛
|
||||||
-- team: 10 设备,不限时长,无广告门槛
|
-- team: 10 设备,不限时长,无广告门槛
|
||||||
INSERT INTO plans (code, max_devices, daily_minutes, ad_gate)
|
INSERT INTO plans (code, max_devices, daily_minutes, ad_gate)
|
||||||
VALUES
|
VALUES
|
||||||
('free', 1, 10, TRUE),
|
('free', 1, 10, TRUE),
|
||||||
('pro', 5, NULL, FALSE),
|
('pro', 3, NULL, FALSE),
|
||||||
('team', 10, NULL, FALSE)
|
('team', 10, NULL, FALSE)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
max_devices = VALUES(max_devices),
|
max_devices = VALUES(max_devices),
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 回退:pro 套餐设备上限 3 → 5。
|
||||||
|
UPDATE plans SET max_devices = 5 WHERE code = 'pro';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- pro 套餐设备上限 5 → 3(已迁移库更新;新库由 000007_seed 直接 seed 为 3)。
|
||||||
|
UPDATE plans SET max_devices = 3 WHERE code = 'pro';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
-- plans seed(free/pro/team)
|
-- plans seed(free/pro/team)
|
||||||
INSERT INTO plans (code, max_devices, daily_minutes, ad_gate) VALUES
|
INSERT INTO plans (code, max_devices, daily_minutes, ad_gate) VALUES
|
||||||
('free', 1, 10, 1),
|
('free', 1, 10, 1),
|
||||||
('pro', 5, NULL, 0),
|
('pro', 3, NULL, 0),
|
||||||
('team', 10, NULL, 0)
|
('team', 10, NULL, 0)
|
||||||
ON CONFLICT(code) DO UPDATE SET
|
ON CONFLICT(code) DO UPDATE SET
|
||||||
max_devices = excluded.max_devices,
|
max_devices = excluded.max_devices,
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 回退:pro 套餐设备上限 3 → 5。
|
||||||
|
UPDATE plans SET max_devices = 5 WHERE code = 'pro';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- pro 套餐设备上限 5 → 3(已迁移库更新;新库由 000007_seed 直接 seed 为 3)。
|
||||||
|
UPDATE plans SET max_devices = 3 WHERE code = 'pro';
|
||||||
Reference in New Issue
Block a user