feat: add configurable verification and redemption codes

This commit is contained in:
237899745
2026-07-25 13:57:39 +08:00
parent d1f093685d
commit c6e96464d3
32 changed files with 2108 additions and 261 deletions

View File

@@ -0,0 +1,82 @@
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;