BEGIN; INSERT INTO system_config (key, value, description) VALUES ('auth', '{"email_verification_required": true}', '认证功能开关') ON CONFLICT (key) DO NOTHING; ALTER TABLE usage_periods ADD COLUMN IF NOT EXISTS grant_used_units INTEGER NOT NULL DEFAULT 0; DO $$ BEGIN ALTER TABLE usage_periods ADD CONSTRAINT usage_periods_grant_used_units_nonnegative CHECK (grant_used_units >= 0 AND grant_used_units <= used_units); EXCEPTION WHEN duplicate_object THEN NULL; END $$; CREATE TABLE IF NOT EXISTS redemption_codes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code_hash VARCHAR(64) NOT NULL UNIQUE, code_hint VARCHAR(32) NOT NULL, benefit_kind VARCHAR(16) NOT NULL CHECK (benefit_kind IN ('plan', 'units')), plan_id UUID REFERENCES plans(id), units INTEGER, duration_days INTEGER NOT NULL, redeem_before TIMESTAMPTZ, is_active BOOLEAN NOT NULL DEFAULT true, note TEXT, created_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CHECK (duration_days BETWEEN 1 AND 3650), CHECK ( (benefit_kind = 'plan' AND plan_id IS NOT NULL AND units IS NULL) OR (benefit_kind = 'units' AND plan_id IS NULL AND units BETWEEN 1 AND 10000000) ) ); CREATE INDEX IF NOT EXISTS idx_redemption_codes_created_at ON redemption_codes(created_at DESC); CREATE INDEX IF NOT EXISTS idx_redemption_codes_active_deadline ON redemption_codes(is_active, redeem_before); CREATE TABLE IF NOT EXISTS redemption_records ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code_id UUID NOT NULL UNIQUE REFERENCES redemption_codes(id), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, benefit_kind VARCHAR(16) NOT NULL CHECK (benefit_kind IN ('plan', 'units')), plan_id UUID REFERENCES plans(id), units INTEGER, benefit_starts_at TIMESTAMPTZ NOT NULL, benefit_expires_at TIMESTAMPTZ NOT NULL, redeemed_ip INET, redeemed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CHECK (benefit_expires_at > benefit_starts_at) ); CREATE INDEX IF NOT EXISTS idx_redemption_records_user_time ON redemption_records(user_id, redeemed_at DESC); CREATE TABLE IF NOT EXISTS unit_grants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, redemption_record_id UUID NOT NULL UNIQUE REFERENCES redemption_records(id) ON DELETE CASCADE, total_units INTEGER NOT NULL CHECK (total_units > 0), remaining_units INTEGER NOT NULL CHECK (remaining_units >= 0 AND remaining_units <= total_units), starts_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CHECK (expires_at > starts_at) ); CREATE INDEX IF NOT EXISTS idx_unit_grants_available ON unit_grants(user_id, expires_at, created_at) WHERE remaining_units > 0; COMMIT;