-- Run this migration after the original schema.sql has already been created. -- PostgreSQL 12+; safe to run once on the existing database. BEGIN; CREATE TABLE IF NOT EXISTS app_users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email VARCHAR(320) NOT NULL, display_name VARCHAR(120), password_hash TEXT, role VARCHAR(20) NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')), is_active BOOLEAN NOT NULL DEFAULT TRUE, email_verified_at TIMESTAMPTZ, last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_app_users_email UNIQUE (email) ); CREATE TABLE IF NOT EXISTS newsletter_recipients ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id BIGINT REFERENCES app_users(id) ON DELETE SET NULL, email VARCHAR(320) NOT NULL, display_name VARCHAR(120), enabled BOOLEAN NOT NULL DEFAULT TRUE, send_time TIME NOT NULL DEFAULT TIME '07:30', timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai', send_all_modules BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_newsletter_recipients_email UNIQUE (email) ); CREATE TABLE IF NOT EXISTS newsletter_modules ( module_key VARCHAR(40) PRIMARY KEY, module_name VARCHAR(120) NOT NULL, description TEXT, enabled BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); INSERT INTO newsletter_modules (module_key, module_name, description, sort_order) VALUES ('general', '综合热点', '国内外综合资讯', 10), ('ai_agent', 'AI 与 Agent', '大模型、AI 应用和 Agent', 20), ('agri_hardware', '农业硬件', '传感器、无人机、机器人和农机', 30), ('agri_solutions', '农业方案落地', '智慧农场、温室、灌溉和数字化方案', 40), ('agri_services', '农业服务', '农业 SaaS、农技、托管、供应链和运维服务', 50), ('policy_market', '政策与市场', '政策、补贴、投资、招标和市场动态', 60), ('domestic', '国内资讯', '国内来源或国内市场相关资讯', 70), ('international', '国际资讯', '国际来源或国际市场相关资讯', 80) ON CONFLICT (module_key) DO NOTHING; CREATE TABLE IF NOT EXISTS recipient_modules ( recipient_id BIGINT NOT NULL REFERENCES newsletter_recipients(id) ON DELETE CASCADE, module_key VARCHAR(40) NOT NULL REFERENCES newsletter_modules(module_key) ON DELETE RESTRICT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (recipient_id, module_key) ); ALTER TABLE email_deliveries ADD COLUMN IF NOT EXISTS recipient_id BIGINT, ADD COLUMN IF NOT EXISTS modules TEXT[] NOT NULL DEFAULT '{}'; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'fk_email_deliveries_recipient' ) THEN ALTER TABLE email_deliveries ADD CONSTRAINT fk_email_deliveries_recipient FOREIGN KEY (recipient_id) REFERENCES newsletter_recipients(id) ON DELETE SET NULL; END IF; END $$; CREATE INDEX IF NOT EXISTS idx_newsletter_recipients_user ON newsletter_recipients (user_id); CREATE INDEX IF NOT EXISTS idx_newsletter_recipients_enabled ON newsletter_recipients (enabled, send_time); CREATE INDEX IF NOT EXISTS idx_recipient_modules_module ON recipient_modules (module_key); COMMIT;