Initial daily news digest web

This commit is contained in:
wuyanwanwu
2026-08-09 17:43:21 +08:00
commit 8e735ece88
17 changed files with 1230 additions and 0 deletions
@@ -0,0 +1,85 @@
-- 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;