-- ARK 資料庫 initial schema CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE TABLE admins ( id SERIAL PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE categories ( id SERIAL PRIMARY KEY, slug TEXT UNIQUE NOT NULL, name_zh_tw TEXT NOT NULL, name_zh_cn TEXT, name_en TEXT, description_zh_tw TEXT, icon_key TEXT DEFAULT 'folder', sort_order INT NOT NULL DEFAULT 0, is_visible BOOLEAN NOT NULL DEFAULT TRUE, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE tags ( id SERIAL PRIMARY KEY, name TEXT UNIQUE NOT NULL, slug TEXT UNIQUE NOT NULL ); CREATE TABLE resources ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), category_id INT NOT NULL REFERENCES categories(id), title TEXT NOT NULL, description TEXT, type TEXT NOT NULL, language TEXT NOT NULL DEFAULT 'zh-TW', cover_image TEXT, file_url TEXT, preview_url TEXT, external_url TEXT, body_text TEXT, badge_label TEXT, is_public BOOLEAN NOT NULL DEFAULT TRUE, is_downloadable BOOLEAN NOT NULL DEFAULT TRUE, is_recommended BOOLEAN NOT NULL DEFAULT FALSE, sort_order INT NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'draft', published_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), view_count INT NOT NULL DEFAULT 0, download_count INT NOT NULL DEFAULT 0, favorite_count INT NOT NULL DEFAULT 0, share_count INT NOT NULL DEFAULT 0 ); CREATE INDEX idx_resources_category ON resources(category_id); CREATE INDEX idx_resources_status_public ON resources(status, is_public); CREATE INDEX idx_resources_published ON resources(published_at DESC NULLS LAST); CREATE INDEX idx_resources_recommended ON resources(is_recommended) WHERE is_recommended = TRUE; CREATE TABLE resource_tags ( resource_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE, tag_id INT NOT NULL REFERENCES tags(id) ON DELETE CASCADE, PRIMARY KEY (resource_id, tag_id) ); CREATE TABLE search_logs ( id BIGSERIAL PRIMARY KEY, query TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Seed categories (from 文案) INSERT INTO categories (slug, name_zh_tw, name_zh_cn, name_en, description_zh_tw, icon_key, sort_order) VALUES ('project-ppt', '項目資料(PPT)', '项目资料(PPT)', 'Project (PPT)', 'ARK 項目介紹、簡報與對外展示資料', 'folder', 1), ('daily-class', '每日課堂(空課)', '每日课堂(空课)', 'Daily class', '每日課堂、夜聊、培訓與回放', 'calendar', 2), ('official-announcement', '官方公告(推特)', '官方公告(推特)', 'Official news', '官方公告、推文與重要通知', 'megaphone', 3), ('academy-materials', '學堂教育(教材)', '学堂教育(教材)', 'Academy materials', '學堂教材、版書與培訓文檔', 'graduation', 4), ('global-evangelism', '全球布道(影片)', '全球布道(影片)', 'Global outreach', '布道與活動影片', 'globe', 5), ('daily-poster', '每日海報', '每日海报', 'Daily posters', '每日宣傳與活動海報', 'image', 6), ('community-tweets', '社群動向(推文)', '社群动向(推文)', 'Community', '社群推文與推廣文案', 'chat', 7), ('video-hub', '視頻匯總(影片)', '视频汇总(影片)', 'Video hub', '影片資料匯總', 'film', 8), ('subsidy-policy', '補貼政策(活動)', '补贴政策(活动)', 'Campaigns', '補貼、活動規則與激勵方案', 'gift', 9), ('how-to', '操作指南(教程)', '操作指南(教程)', 'Tutorials', '操作教學與使用流程', 'book', 10), ('official-assets', '官方物料(物料)', '官方物料(物料)', 'Brand assets', 'Logo、模板與品牌素材', 'palette', 11), ('media-coverage', '媒體收錄(新聞)', '媒体收录(新闻)', 'Press', '外部媒體與新聞報導', 'newspaper', 12), ('academy-video', '學堂教育(影片)', '学堂教育(影片)', 'Academy video', '學堂課程與培訓影片', 'play', 13), ('general', 'General', 'General', 'General', '綜合或未分類資料', 'hash', 14); INSERT INTO tags (name, slug) VALUES ('官方推薦', 'official'), ('新人必看', 'newcomer'), ('可下載', 'downloadable'), ('公告', 'announcement'), ('教程', 'tutorial'), ('海報', 'poster'); -- Demo resources (published) INSERT INTO resources (category_id, title, description, type, language, cover_image, file_url, is_public, is_downloadable, is_recommended, status, published_at, badge_label, sort_order) SELECT c.id, 'ARK 項目介紹簡報(示例)', '適合線下宣講與新人培訓。', 'ppt', 'zh-TW', '/uploads/placeholder-cover.svg', '/uploads/placeholder-cover.svg', TRUE, TRUE, TRUE, 'published', NOW() - INTERVAL '2 days', '新人必看', 10 FROM categories c WHERE c.slug = 'project-ppt' LIMIT 1; INSERT INTO resources (category_id, title, description, type, language, cover_image, preview_url, is_public, is_downloadable, is_recommended, status, published_at, badge_label, sort_order) SELECT c.id, '每日海報 | 示例', '適合每日社群推廣轉發。', 'image', 'zh-TW', '/uploads/placeholder-cover.svg', '/uploads/placeholder-cover.svg', TRUE, TRUE, TRUE, 'published', NOW() - INTERVAL '1 day', '最新公告', 20 FROM categories c WHERE c.slug = 'daily-poster' LIMIT 1; INSERT INTO resources (category_id, title, description, type, language, external_url, is_public, is_downloadable, is_recommended, status, published_at, sort_order) SELECT c.id, '外部報導連結(示例)', '第三方媒體文章(示例)。', 'link', 'zh-TW', 'https://example.com', TRUE, FALSE, FALSE, 'published', NOW() - INTERVAL '3 hours', 5 FROM categories c WHERE c.slug = 'media-coverage' LIMIT 1; INSERT INTO resources (category_id, title, description, type, language, cover_image, file_url, is_public, is_downloadable, status, published_at, sort_order) SELECT c.id, '全球布道影片(示例)', '活動精華剪輯。', 'video', 'zh-TW', '/uploads/placeholder-cover.svg', 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4', TRUE, TRUE, 'published', NOW() - INTERVAL '5 days', 8 FROM categories c WHERE c.slug = 'global-evangelism' LIMIT 1;