---
# System prepended metadata

title: v1-SQL Schema

---

# v1-SQL Schema

## Member

```sql
-- 會員表
CREATE TABLE members (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    full_name VARCHAR(100),
    --* 看有沒有支援 ENUM、確定系統角色是否可重複，另外統一 org 或是 com
    member_type VARCHAR(20) NOT NULL DEFAULT 'general', -- 'general', 'company_user', 'company_admin'
    is_verified BOOLEAN DEFAULT FALSE,
    is_active BOOLEAN DEFAULT TRUE,
    avatar_url VARCHAR(255),
    bio TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_login_at TIMESTAMP WITH TIME ZONE,
    last_active_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 會員驗證表
CREATE TABLE member_verifications (
    --* 可能不需要 id, 可以直接用 member_id 當 PK
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL REFERENCES members(id) ON DELETE CASCADE,
    verification_type VARCHAR(20) NOT NULL, -- 'email', 'password_reset'
    verification_code VARCHAR(100) NOT NULL,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    is_used BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 會員會話表
--* 要跟宇衡確定 JWT refresh token 的 session 要存那哪些欄位
CREATE TABLE member_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL REFERENCES members(id) ON DELETE CASCADE,
    session_token VARCHAR(255) UNIQUE NOT NULL,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    ip_address INET,
    user_agent TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 會員通知設定表 (對應個人設定頁面)
CREATE TABLE member_notification_settings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL REFERENCES members(id) ON DELETE CASCADE,
    email_notifications BOOLEAN DEFAULT TRUE, -- Email Notifications
    activity_updates BOOLEAN DEFAULT TRUE,    -- Activity Updates  
    marketing_emails BOOLEAN DEFAULT FALSE,   -- Marketing Emails
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

--* 可能需要多一張表、存 org-member 的對應提升效能
--* 例如列出某個 org 底下的所有 member 時，若 member DB 中有紀錄 org-member，可以直接 SQL 一次撈
--* 但如果只有 org DB 中有 member-org，需要一筆一筆跟 member svc 拿 member data

-- 索引
CREATE INDEX idx_members_email ON members(email);
CREATE INDEX idx_members_username ON members(username);
CREATE INDEX idx_members_type ON members(member_type);
CREATE INDEX idx_members_active ON members(is_active);
CREATE INDEX idx_members_last_active ON members(last_active_at);
CREATE INDEX idx_member_verifications_code ON member_verifications(verification_code);
CREATE INDEX idx_member_sessions_token ON member_sessions(session_token);
CREATE INDEX idx_member_sessions_member_id ON member_sessions(member_id);
```

## Organization

```sql
-- 企業組織表 (對應Company Profile頁面)
CREATE TABLE organizations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,                    -- Company Name
    industry VARCHAR(50),                          -- Industry
    website VARCHAR(255),                          -- Company Website  
    description TEXT,                              -- Company Description
    admin_member_id UUID NOT NULL,                 -- 企業後台管理者
    --* 看能不能 ENUM、或是用 true/false
    subscription_plan VARCHAR(20) DEFAULT 'free',  -- 'free', 'premium' 
    is_active BOOLEAN DEFAULT TRUE,
    logo_url VARCHAR(255),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 組織成員表 (對應Team Members和User Management頁面)
--* 若 member 也存 org-member，可以斟酌這邊完全保留、或是只留設定的部分
CREATE TABLE organization_members (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    member_id UUID NOT NULL, -- 參考 Member Service 的 member.id
    role VARCHAR(20) NOT NULL DEFAULT 'member', -- 'admin', 'member'
    
    -- 權限設定 (對應Permissions欄位)
    can_post BOOLEAN DEFAULT FALSE,              -- Can post
    can_moderate BOOLEAN DEFAULT FALSE,          -- Can moderate  
    can_invite BOOLEAN DEFAULT FALSE,            -- Can invite
    
    status VARCHAR(20) DEFAULT 'pending',        -- 'pending', 'active', 'inactive' (對應Status欄位)
    joined_at TIMESTAMP WITH TIME ZONE,
    invited_by UUID,                             -- 邀請者ID
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(organization_id, member_id)
);

-- 成員邀請表
--* 同樣的、可以討論 member DB 是否需要一份 replica
CREATE TABLE member_invitations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    inviter_member_id UUID NOT NULL, -- 邀請者
    invitee_email VARCHAR(100) NOT NULL,
    invitation_token VARCHAR(255) UNIQUE NOT NULL,
    status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'accepted', 'rejected', 'expired'
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 組織設定表
CREATE TABLE organization_settings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    
    -- 安全設定 (對應Account Settings頁面)
    two_factor_authentication BOOLEAN DEFAULT FALSE,
    api_access BOOLEAN DEFAULT FALSE,
    email_notifications BOOLEAN DEFAULT TRUE,
    
    settings JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_organizations_admin ON organizations(admin_member_id);
CREATE INDEX idx_org_members_org_id ON organization_members(organization_id);
CREATE INDEX idx_org_members_member_id ON organization_members(member_id);
CREATE INDEX idx_org_members_status ON organization_members(status);
CREATE INDEX idx_invitations_token ON member_invitations(invitation_token);
CREATE INDEX idx_invitations_email ON member_invitations(invitee_email);
```

## Admin

```sql
-- 系統管理員表
CREATE TABLE system_admins (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    role VARCHAR(20) DEFAULT 'admin', -- 'super_admin', 'admin', 'moderator'
    permissions JSONB,
    is_active BOOLEAN DEFAULT TRUE,
    last_login_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 內容審核表 (支援Q&A的Approved/Pending Review狀態)
CREATE TABLE content_reviews (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content_type VARCHAR(20) NOT NULL, -- 'experience_post', 'qa_discussion'
    content_id UUID NOT NULL,
    content_service VARCHAR(50) NOT NULL, -- 來源服務名稱
    reviewer_id UUID REFERENCES system_admins(id),
    
    -- AI檢測結果
    ai_score DECIMAL(3,2), -- AI評分 0-1
    ai_analysis JSONB, -- AI分析結果
    is_sustainability_related BOOLEAN DEFAULT FALSE,
    
    -- 審核狀態
    manual_review_required BOOLEAN DEFAULT FALSE,
    status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved', 'rejected', 'flagged'
    review_notes TEXT,
    
    reviewed_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 系統設定表
CREATE TABLE system_settings (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    setting_key VARCHAR(100) UNIQUE NOT NULL,
    setting_value JSONB NOT NULL,
    description TEXT,
    is_public BOOLEAN DEFAULT FALSE,
    updated_by UUID REFERENCES system_admins(id),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 系統日誌表
--! 改用 ES，直接 log 出來 EFK 收
CREATE TABLE system_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    service_name VARCHAR(50) NOT NULL,
    log_level VARCHAR(10) NOT NULL, -- 'info', 'warning', 'error', 'critical'
    message TEXT NOT NULL,
    metadata JSONB,
    member_id UUID, -- 關聯的會員ID
    organization_id UUID, -- 關聯的組織ID
    ip_address INET,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 企業審核表
CREATE TABLE organization_reviews (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL,
    reviewer_id UUID REFERENCES system_admins(id),
    review_type VARCHAR(20) NOT NULL, -- 'registration', 'subscription_upgrade'
    status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved', 'rejected'
    review_notes TEXT,
    reviewed_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_content_reviews_content ON content_reviews(content_type, content_id);
CREATE INDEX idx_content_reviews_status ON content_reviews(status);
CREATE INDEX idx_content_reviews_reviewer ON content_reviews(reviewer_id);
CREATE INDEX idx_system_logs_service ON system_logs(service_name);
CREATE INDEX idx_system_logs_level ON system_logs(log_level);
CREATE INDEX idx_system_logs_created ON system_logs(created_at);
CREATE INDEX idx_org_reviews_org_id ON organization_reviews(organization_id);
CREATE INDEX idx_org_reviews_status ON organization_reviews(status);
```

## Content

```sql
-- 標籤表 (對應hashtag功能)
CREATE TABLE tags (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(50) UNIQUE NOT NULL,
    usage_count INTEGER DEFAULT 0,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

--* 有沒有機會 ES/OpenSearch 或 Mongo 全文搜尋更快 還能 RAG (x
-- 經驗分享文章表 (對應Experience Sharing頁面)
CREATE TABLE experience_posts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    author_member_id UUID NOT NULL, -- 參考 Member Service
    organization_id UUID,           -- 參考 Organization Service (發文者所屬組織)
    title VARCHAR(200),
    content TEXT NOT NULL,
    media_urls JSONB,               -- 存儲圖片和影片URL
    status VARCHAR(20) DEFAULT 'published', -- 'draft', 'published', 'archived'
    
    -- 互動統計
    view_count INTEGER DEFAULT 0,
    like_count INTEGER DEFAULT 0,
    comment_count INTEGER DEFAULT 0,
    share_count INTEGER DEFAULT 0,
    
    -- AI檢測
    is_sdg_related BOOLEAN DEFAULT FALSE,
    ai_analysis JSONB,
    
    published_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 文章標籤關聯表 (支援hashtag功能)
CREATE TABLE post_tag_relations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    post_id UUID NOT NULL REFERENCES experience_posts(id) ON DELETE CASCADE,
    tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(post_id, tag_id)
);

--* 這感覺也能用 no-SQL
-- 文章留言表 (對應Comments功能)
CREATE TABLE post_comments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    post_id UUID NOT NULL REFERENCES experience_posts(id) ON DELETE CASCADE,
    author_member_id UUID NOT NULL, -- 參考 Member Service
    content TEXT NOT NULL CHECK (LENGTH(content) <= 100), -- 100字限制
    like_count INTEGER DEFAULT 0,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Q&A討論表 (對應Q&A Zone頁面)
CREATE TABLE qa_discussions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    author_member_id UUID NOT NULL, -- 參考 Member Service
    organization_id UUID,           -- 參考 Organization Service
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    
    -- 標籤 (如 #sustainability, #manufacturing)
    tags JSONB,
    
    -- 狀態管理
    status VARCHAR(20) DEFAULT 'published', -- 'pending', 'published', 'approved', 'rejected'
    approval_status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved' (對應Approved標籤)
    
    -- 統計
    view_count INTEGER DEFAULT 0,
    answer_count INTEGER DEFAULT 0,
    like_count INTEGER DEFAULT 0,
    
    is_resolved BOOLEAN DEFAULT FALSE,
    is_sustainability_related BOOLEAN DEFAULT FALSE, -- AI檢測結果
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Q&A回覆表 (對應Responses功能)
CREATE TABLE qa_answers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    discussion_id UUID NOT NULL REFERENCES qa_discussions(id) ON DELETE CASCADE,
    author_member_id UUID NOT NULL, -- 參考 Member Service
    content TEXT NOT NULL CHECK (LENGTH(content) <= 100), -- 100字限制
    like_count INTEGER DEFAULT 0,
    is_best_answer BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 會員互動表 (點讚、分享等)
CREATE TABLE member_interactions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL, -- 參考 Member Service
    target_type VARCHAR(20) NOT NULL, -- 'post', 'comment', 'qa_discussion', 'qa_answer'
    target_id UUID NOT NULL,
    interaction_type VARCHAR(20) NOT NULL, -- 'like', 'share', 'bookmark'
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(member_id, target_type, target_id, interaction_type)
);

-- 索引
CREATE INDEX idx_experience_posts_author ON experience_posts(author_member_id);
CREATE INDEX idx_experience_posts_org ON experience_posts(organization_id);
CREATE INDEX idx_experience_posts_status ON experience_posts(status);
CREATE INDEX idx_experience_posts_published ON experience_posts(published_at);
CREATE INDEX idx_post_comments_post_id ON post_comments(post_id);
CREATE INDEX idx_post_comments_author ON post_comments(author_member_id);
CREATE INDEX idx_qa_discussions_author ON qa_discussions(author_member_id);
CREATE INDEX idx_qa_discussions_status ON qa_discussions(status);
CREATE INDEX idx_qa_discussions_approval ON qa_discussions(approval_status);
CREATE INDEX idx_qa_answers_discussion ON qa_answers(discussion_id);
CREATE INDEX idx_qa_answers_author ON qa_answers(author_member_id);
CREATE INDEX idx_member_interactions_member ON member_interactions(member_id);

-- 全文搜索索引 (支援搜索功能)
CREATE INDEX idx_experience_posts_search ON experience_posts USING gin(to_tsvector('english', COALESCE(title, '') || ' ' || content));
CREATE INDEX idx_qa_discussions_search ON qa_discussions USING gin(to_tsvector('english', title || ' ' || content));
```

## Exchange

```sql
-- 會員點數表
-- * 可以考慮放 member (視後續 API 功能決定)
CREATE TABLE member_points (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL, -- 參考 Member Service
    total_points INTEGER DEFAULT 0,
    available_points INTEGER DEFAULT 0,
    used_points INTEGER DEFAULT 0,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 會員點數歷史記錄
CREATE TABLE member_point_history (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL, -- 參考 Member Service
    point_change INTEGER NOT NULL,
    point_type VARCHAR(20) NOT NULL, -- 'earned', 'used', 'expired'
    source_type VARCHAR(50), -- 'event_participation', 'exchange', 'manual_adjustment'
    source_id UUID, -- 關聯的活動或兌換記錄ID
    description TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 兌換商品分類表
CREATE TABLE exchange_categories (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 兌換商品表
CREATE TABLE exchange_items (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID, -- 參考 Organization Service (提供商品的組織)
    category_id UUID REFERENCES exchange_categories(id),
    name VARCHAR(100) NOT NULL,
    description TEXT NOT NULL,
    points_required INTEGER NOT NULL,
    stock_quantity INTEGER DEFAULT 0,
    unlimited_stock BOOLEAN DEFAULT FALSE,
    image_urls JSONB,
    terms_and_conditions TEXT,
    status VARCHAR(20) DEFAULT 'active', -- 'active', 'inactive', 'out_of_stock'
    valid_until TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 兌換記錄表
CREATE TABLE exchange_orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL, -- 參考 Member Service
    item_id UUID NOT NULL REFERENCES exchange_items(id),
    organization_id UUID, -- 參考 Organization Service
    points_used INTEGER NOT NULL,
    quantity INTEGER DEFAULT 1,
    status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'confirmed', 'shipped', 'completed', 'cancelled'
    delivery_info JSONB, -- 配送資訊
    tracking_number VARCHAR(100),
    notes TEXT,
    ordered_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 兌換記錄狀態歷史
CREATE TABLE exchange_order_history (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id UUID NOT NULL REFERENCES exchange_orders(id) ON DELETE CASCADE,
    previous_status VARCHAR(20),
    new_status VARCHAR(20) NOT NULL,
    changed_by UUID, -- 操作者ID
    notes TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 庫存變動記錄
CREATE TABLE exchange_stock_movements (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    item_id UUID NOT NULL REFERENCES exchange_items(id) ON DELETE CASCADE,
    movement_type VARCHAR(20) NOT NULL, -- 'add', 'remove', 'exchange', 'adjustment'
    quantity_change INTEGER NOT NULL,
    reference_id UUID, -- 關聯的訂單或其他記錄ID
    notes TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_member_points_member_id ON member_points(member_id);
CREATE INDEX idx_member_point_history_member_id ON member_point_history(member_id);
CREATE INDEX idx_member_point_history_type ON member_point_history(point_type);
CREATE INDEX idx_exchange_items_org_id ON exchange_items(organization_id);
CREATE INDEX idx_exchange_items_category ON exchange_items(category_id);
CREATE INDEX idx_exchange_items_status ON exchange_items(status);
CREATE INDEX idx_exchange_orders_member ON exchange_orders(member_id);
CREATE INDEX idx_exchange_orders_item ON exchange_orders(item_id);
CREATE INDEX idx_exchange_orders_status ON exchange_orders(status);
CREATE INDEX idx_exchange_orders_org ON exchange_orders(organization_id);
CREATE INDEX idx_exchange_order_history_order ON exchange_order_history(order_id);
CREATE INDEX idx_stock_movements_item ON exchange_stock_movements(item_id);
```

## Analytics

```sql

-- 首頁統計數據表 (對應首頁儀表板數據)
CREATE TABLE dashboard_stats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    date DATE NOT NULL,
    organization_id UUID, -- 可選，組織級別統計
    
    -- 對應首頁顯示的數據
    experience_shared_count INTEGER DEFAULT 0,    -- Experience Shared: 245
    questions_asked_count INTEGER DEFAULT 0,      -- Questions Asked: 128  
    active_users_count INTEGER DEFAULT 0,         -- Active Users: 1,024
    
    -- 其他統計
    total_members INTEGER DEFAULT 0,
    new_members_today INTEGER DEFAULT 0,
    total_posts INTEGER DEFAULT 0,
    total_comments INTEGER DEFAULT 0,
    total_likes INTEGER DEFAULT 0,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(date, organization_id)
);

-- 會員活動統計表
CREATE TABLE member_activity_stats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    member_id UUID NOT NULL, -- 參考 Member Service
    date DATE NOT NULL,
    
    -- 活動統計
    posts_created INTEGER DEFAULT 0,
    comments_made INTEGER DEFAULT 0,
    qa_questions_asked INTEGER DEFAULT 0,
    qa_answers_given INTEGER DEFAULT 0,
    likes_given INTEGER DEFAULT 0,
    shares_made INTEGER DEFAULT 0,
    login_count INTEGER DEFAULT 0,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(member_id, date)
);

-- 內容統計表 (支援Featured Content)
CREATE TABLE content_stats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    date DATE NOT NULL,
    organization_id UUID, -- 可選
    
    -- 內容統計
    experience_posts_created INTEGER DEFAULT 0,
    qa_discussions_created INTEGER DEFAULT 0,
    comments_created INTEGER DEFAULT 0,
    qa_answers_created INTEGER DEFAULT 0,
    
    -- 互動統計
    total_views INTEGER DEFAULT 0,
    total_likes INTEGER DEFAULT 0,
    total_shares INTEGER DEFAULT 0,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(date, organization_id)
);

-- 搜索統計表 (支援搜索功能分析)
CREATE TABLE search_stats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    search_query VARCHAR(255) NOT NULL,
    search_type VARCHAR(20) NOT NULL, -- 'post', 'qa', 'tag', 'hashtag'
    result_count INTEGER DEFAULT 0,
    member_id UUID, -- 參考 Member Service
    organization_id UUID, -- 參考 Organization Service
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 熱門內容表 (支援Community Highlights)
CREATE TABLE trending_content (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content_type VARCHAR(20) NOT NULL, -- 'experience_post', 'qa_discussion'
    content_id UUID NOT NULL,
    organization_id UUID, -- 參考 Organization Service
    trend_score DECIMAL(10,4) DEFAULT 0, -- 熱門分數
    trend_period VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly'
    
    -- 計算熱門度的因子
    view_count INTEGER DEFAULT 0,
    like_count INTEGER DEFAULT 0,
    comment_count INTEGER DEFAULT 0,
    share_count INTEGER DEFAULT 0,
    
    calculated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    valid_until TIMESTAMP WITH TIME ZONE NOT NULL,
    
    UNIQUE(content_type, content_id, trend_period)
);

-- 索引
CREATE INDEX idx_dashboard_stats_date ON dashboard_stats(date);
CREATE INDEX idx_dashboard_stats_org ON dashboard_stats(organization_id);
CREATE INDEX idx_member_activity_member ON member_activity_stats(member_id);
CREATE INDEX idx_member_activity_date ON member_activity_stats(date);
CREATE INDEX idx_content_stats_date ON content_stats(date);
CREATE INDEX idx_content_stats_org ON content_stats(organization_id);
CREATE INDEX idx_search_stats_query ON search_stats(search_query);
CREATE INDEX idx_search_stats_type ON search_stats(search_type);
CREATE INDEX idx_search_stats_created ON search_stats(created_at);
CREATE INDEX idx_trending_content_type ON trending_content(content_type);
CREATE INDEX idx_trending_content_score ON trending_content(trend_score);
CREATE INDEX idx_trending_content_period ON trending_content(trend_period);
```



# 問題統整

## Member DB 問題

1. **`--* 看有沒有支援 ENUM、確定系統角色是否可重複，另外統一 org 或是 com`**
   - 位置：members 表的 member_type 欄位
   - 問題：需要確認資料庫是否支援 ENUM 類型、系統角色重複性、命名統一性

2. **`--* 可能不需要 id, 可以直接用 member_id 當 PK`**
   - 位置：member_verifications 表
   - 問題：考慮是否移除獨立的 id 欄位，直接使用 member_id 作為主鍵

3. **`--* 要跟宇衡確定 JWT refresh token 的 session 要存那哪些欄位`**
   - 位置：member_sessions 表
   - 問題：需要與宇衡確認 JWT refresh token 機制所需的欄位

4. **`--* 可能需要多一張表、存 org-member 的對應提升效能`**
   - 位置：索引區塊前
   - 問題：考慮是否需要額外的組織-會員對應表來提升查詢效能

5. **`--* 例如列出某個 org 底下的所有 member 時，若 member DB 中有紀錄 org-member，可以直接 SQL 一次撈`**
   - 位置：同上
   - 問題：說明效能考量的具體場景

6. **`--* 但如果只有 org DB 中有 member-org，需要一筆一筆跟 member svc 拿 member data`**
   - 位置：同上
   - 問題：說明現有架構可能的效能瓶頸

## Organization DB 問題

7. **`--* 看能不能 ENUM、或是用 true/false`**
   - 位置：organizations 表的 subscription_plan 欄位
   - 問題：考慮使用 ENUM 或布林值來替代 VARCHAR

8. **`--* 若 member 也存 org-member，可以斟酌這邊完全保留、或是只留設定的部分`**
   - 位置：organization_members 表
   - 問題：如果 member DB 也存儲組織關聯，需要考慮資料重複性

9. **`--* 同樣的、可以討論 member DB 是否需要一份 replica`**
   - 位置：member_invitations 表
   - 問題：考慮在 member DB 中是否需要邀請資料的副本

## Content DB 問題

10. **`--* 有沒有機會 ES/OpenSearch 或 Mongo 全文搜尋更快 還能 RAG (x`**
    - 位置：tags 表註解
    - 問題：考慮使用 Elasticsearch/OpenSearch 或 MongoDB 來提升搜尋效能和支援 RAG

11. **`--* 這感覺也能用 no-SQL`**
    - 位置：post_comments 表註解
    - 問題：考慮使用 NoSQL 資料庫來存儲留言資料

## Exchange DB 問題

12. **`--* 可以考慮放 member (視後續 API 功能決定)`**
    - 位置：member_points 表註解
    - 問題：考慮是否將點數資料放在 member service 中

## Admin DB 問題

13. **`--! 改用 ES，直接 log 出來 EFK 收`**
    - 位置：system_logs 表註解
    - 問題：建議改用 Elasticsearch，通過 EFK (Elasticsearch, Fluentd, Kibana) 來收集日誌


# 改進
- ENUM 類型 + 複合索引
- 移除不必要的 UUID 欄位
- 本地存儲組織關聯
- 專用的 token 索引




| 服務 | 原始問題 | 修正項目 | 修正效果 |
|------|----------|----------|----------|
| **Member Service** | `--* 看有沒有支援 ENUM、確定系統角色是否可重複，另外統一 org 或是 com` | 使用 `member_type_enum` 替代 VARCHAR | 數據一致性提升、查詢效能提升 |
| Member Service | `--* 可能不需要 id, 可以直接用 member_id 當 PK` | `member_verifications` 使用複合主鍵 `(member_id, verification_type)` | 節省空間、避免無意義ID |
| Member Service | `--* 要跟宇衡確定 JWT refresh token 的 session 要存那哪些欄位` | 新增 `refresh_token`, `refresh_expires_at`, `token_type` 欄位 | 支援完整JWT流程 |
| Member Service | `--* 可能需要多一張表、存 org-member 的對應提升效能` | 新增 `member_organizations` 表 | 避免跨服務查詢、提升效能 |
| **Organization Service** | `--* 看能不能 ENUM、或是用 true/false` | 使用 `subscription_plan_enum` 或 `is_premium BOOLEAN` | 訂閱狀態管理標準化 |
| Organization Service | `--* 若 member 也存 org-member，可以斟酌這邊完全保留、或是只留設定的部分` | 保留完整 `organization_members` 表，負責詳細權限管理 | 職責分離、避免數據重複 |
| Organization Service | `--* 同樣的、可以討論 member DB 是否需要一份 replica` | 建議不在 Member DB 複製邀請資料 | 維持單一資料源 |
| **Admin Service** | 字串比較效能問題 | 所有狀態欄位使用 ENUM 類型 | 狀態管理標準化、查詢效能提升 |
| Admin Service | `--! 改用 ES，直接 log 出來 EFK 收` | 移除 `system_logs` 表 | 符合 ELK Stack 架構 |
| **Content Service** | `--* 有沒有機會 ES/OpenSearch 或 Mongo 全文搜尋更快 還能 RAG (x` | 保持 PostgreSQL GIN 索引，建議搭配 ES | 搜尋效能提升、支援未來RAG |
| Content Service | `--* 這感覺也能用 no-SQL` | 保持 PostgreSQL 結構 | 維持ACID特性、暫不遷移 |
| Content Service | 標籤關聯表效能 | `post_tag_relations` 使用複合主鍵 `(post_id, tag_id)` | 提升效能、節省空間 |
| **Exchange Service** | `-- * 可以考慮放 member (視後續 API 功能決定)` | `member_points` 直接用 `member_id` 當主鍵，保留在 Exchange Service | 避免跨服務查詢複雜度 |
| Exchange Service | 狀態管理混亂 | 所有狀態欄位使用 ENUM 類型 | 狀態流程標準化 |
| **Analytics Service** | 字串比較效能 | `search_type`, `content_type`, `trend_period` 使用 ENUM | 查詢效能提升 |





---
# v2-SQL Schema

> 接下來以國展想看的 demo 為主，我提議依序搞定 admin、member、organization

## Activity Service Database
```sql
-- 動態表單系統擴展

-- 表單欄位類型枚舉
CREATE TYPE field_type_enum AS ENUM (
    'short_text',      -- 短文字 (單行輸入)
    'long_text',       -- 長文字 (多行輸入)
    'number',          -- 數字
    'email',           -- 電子郵件
    'phone',           -- 電話
    'date',            -- 日期
    'time',            -- 時間
    'datetime',        -- 日期時間
    'single_choice',   -- 單選 (radio)
    'multiple_choice', -- 多選 (checkbox)
    'dropdown',        -- 下拉選單
    'file_upload',     -- 檔案上傳
    'rating',          -- 評分
    'yes_no',          -- 是/否
    'url',             -- 網址
    'address'          -- 地址
);

-- 表單狀態枚舉
CREATE TYPE form_status_enum AS ENUM (
    'draft',           -- 草稿
    'active',          -- 啟用中
    'closed',          -- 已關閉
    'archived'         -- 已封存
);

-- 表單模板表 (定義表單結構)
CREATE TABLE form_templates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL,          -- 所屬組織
    creator_user_id UUID NOT NULL,          -- 創建者
    activity_id UUID REFERENCES activities(id) ON DELETE SET NULL, -- 可選：關聯到特定活動
    
    title VARCHAR(255) NOT NULL,            -- 表單標題
    description TEXT,                       -- 表單說明
    status form_status_enum DEFAULT 'draft', -- 表單狀態
    
    -- 進階設定
    is_public BOOLEAN DEFAULT false,        -- 是否公開 (可匿名填寫)
    allow_multiple_submissions BOOLEAN DEFAULT false, -- 是否允許重複提交
    require_login BOOLEAN DEFAULT true,     -- 是否需要登入才能填寫
    show_progress_bar BOOLEAN DEFAULT false, -- 是否顯示進度條
    submit_button_text VARCHAR(50) DEFAULT '提交', -- 提交按鈕文字
    success_message TEXT DEFAULT '感謝您的提交！', -- 提交成功訊息
    
    -- 時間設定
    open_time TIMESTAMP WITH TIME ZONE,     -- 開放填寫時間
    close_time TIMESTAMP WITH TIME ZONE,    -- 關閉填寫時間
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單欄位表 (定義每個欄位的屬性)
CREATE TABLE form_fields (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_template_id UUID NOT NULL REFERENCES form_templates(id) ON DELETE CASCADE,
    
    field_type field_type_enum NOT NULL,    -- 欄位類型
    label VARCHAR(255) NOT NULL,            -- 欄位標籤/問題
    description TEXT,                       -- 欄位說明/提示文字
    placeholder VARCHAR(255),               -- 佔位符文字
    
    -- 驗證規則
    is_required BOOLEAN DEFAULT false,      -- 是否必填
    min_length INTEGER,                     -- 最小長度 (文字欄位)
    max_length INTEGER,                     -- 最大長度 (文字欄位)
    min_value NUMERIC(15, 2),              -- 最小值 (數字欄位)
    max_value NUMERIC(15, 2),              -- 最大值 (數字欄位)
    validation_regex TEXT,                  -- 自訂驗證正則表達式
    validation_message TEXT,                -- 驗證失敗訊息
    
    -- 排序與顯示
    display_order INTEGER NOT NULL,         -- 顯示順序
    is_visible BOOLEAN DEFAULT true,        -- 是否顯示
    
    -- 條件邏輯 (進階功能)
    conditional_logic JSONB,                -- 條件顯示邏輯 (JSON格式)
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單欄位選項表 (用於單選、多選、下拉選單等)
CREATE TABLE form_field_options (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_field_id UUID NOT NULL REFERENCES form_fields(id) ON DELETE CASCADE,
    
    option_value TEXT NOT NULL,             -- 選項值
    option_label VARCHAR(255) NOT NULL,     -- 選項顯示文字
    display_order INTEGER NOT NULL,         -- 顯示順序
    is_default BOOLEAN DEFAULT false,       -- 是否為預設選項
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單提交記錄表 (儲存每次提交)
CREATE TABLE form_submissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_template_id UUID NOT NULL REFERENCES form_templates(id) ON DELETE CASCADE,
    
    submitter_user_id UUID,                 -- 提交者 (如果已登入)
    submitter_ip VARCHAR(45),               -- 提交者 IP
    submitter_email VARCHAR(255),           -- 提交者 Email (如果收集)
    
    is_completed BOOLEAN DEFAULT false,     -- 是否完成提交 (支援草稿功能)
    submission_data JSONB NOT NULL,         -- 提交的表單資料 (JSON格式，靈活儲存)
    
    submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單提交回應表 (詳細儲存每個欄位的回應)
CREATE TABLE form_field_responses (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_submission_id UUID NOT NULL REFERENCES form_submissions(id) ON DELETE CASCADE,
    form_field_id UUID NOT NULL REFERENCES form_fields(id) ON DELETE CASCADE,
    
    response_value TEXT,                    -- 回應值 (文字形式)
    response_data JSONB,                    -- 回應資料 (JSON格式，支援複雜資料)
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單檔案上傳表 (如果欄位包含檔案上傳)
CREATE TABLE form_file_uploads (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_field_response_id UUID NOT NULL REFERENCES form_field_responses(id) ON DELETE CASCADE,
    
    file_name VARCHAR(255) NOT NULL,        -- 檔案名稱
    file_size BIGINT NOT NULL,              -- 檔案大小 (bytes)
    file_type VARCHAR(100),                 -- 檔案類型 (MIME type)
    file_url TEXT NOT NULL,                 -- 檔案儲存路徑/URL
    
    uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 表單統計表 (快取表單統計資料)
CREATE TABLE form_statistics (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_template_id UUID NOT NULL REFERENCES form_templates(id) ON DELETE CASCADE,
    
    total_views INTEGER DEFAULT 0,          -- 總瀏覽次數
    total_submissions INTEGER DEFAULT 0,    -- 總提交次數
    completion_rate NUMERIC(5, 2),          -- 完成率
    average_completion_time INTEGER,        -- 平均完成時間 (秒)
    
    last_calculated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 索引優化
CREATE INDEX idx_form_templates_org_id ON form_templates(organization_id);
CREATE INDEX idx_form_templates_activity_id ON form_templates(activity_id);
CREATE INDEX idx_form_templates_status ON form_templates(status);
CREATE INDEX idx_form_fields_template_id ON form_fields(form_template_id);
CREATE INDEX idx_form_fields_display_order ON form_fields(form_template_id, display_order);
CREATE INDEX idx_form_field_options_field_id ON form_field_options(form_field_id);
CREATE INDEX idx_form_submissions_template_id ON form_submissions(form_template_id);
CREATE INDEX idx_form_submissions_user_id ON form_submissions(submitter_user_id);
CREATE INDEX idx_form_submissions_submitted_at ON form_submissions(submitted_at);
CREATE INDEX idx_form_field_responses_submission_id ON form_field_responses(form_submission_id);
CREATE INDEX idx_form_file_uploads_response_id ON form_file_uploads(form_field_response_id);

-- JSONB 索引 (加速 JSON 查詢)
CREATE INDEX idx_form_submissions_data ON form_submissions USING gin(submission_data);
CREATE INDEX idx_form_field_responses_data ON form_field_responses USING gin(response_data);

-- 自動更新 updated_at 的觸發器函數
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ language 'plpgsql';

-- 為需要的表創建觸發器
CREATE TRIGGER update_form_templates_updated_at BEFORE UPDATE ON form_templates
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_form_fields_updated_at BEFORE UPDATE ON form_fields
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_form_submissions_updated_at BEFORE UPDATE ON form_submissions
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
```

## Admin Service Database
```sql
-- Admin Service Database

-- 平台管理員操作日誌
CREATE TABLE platform_admin_audit_log (
    id BIGSERIAL PRIMARY KEY,
    admin_user_id UUID NOT NULL, -- 執行操作的平台管理員 User ID (來自 Member Service)
    action_type VARCHAR(100) NOT NULL, -- 例如: 'approve_organization', 'reject_organization', 'suspend_user', 'update_system_setting'
    target_entity_type VARCHAR(50), -- 例如: 'organization', 'user', 'article'
    target_entity_id VARCHAR(36),   -- 被操作的實體 ID (應該是 UUID)
    details JSONB,                  -- 操作細節
    ip_address INET,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 系統設定表 (可選，若有全域設定需求)
CREATE TABLE system_settings (
    setting_key VARCHAR(100) PRIMARY KEY,
    setting_value TEXT,
    description TEXT,
    updated_by_admin_user_id UUID,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- (可選) 審核佇列表 (如果需要一個集中的地方管理各類審核任務)
-- 實際上審核狀態更多是分散在各自服務的實體中 (如 Organization 的 status, Article 的 status)
-- 此表更像是一個給平台管理員看的「待辦事項」摘要
-- CREATE TABLE admin_review_queue (
--     id BIGSERIAL PRIMARY KEY,
--     item_type VARCHAR(50) NOT NULL, -- 'organization_application', 'article_flagged', 'question_flagged'
--     item_id UUID NOT NULL,          -- 對應到具體服務中的實體 ID
--     reason_for_review TEXT,
--     assigned_to_admin_user_id UUID,
--     status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'in_progress', 'resolved'
--     created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
--     updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
-- );

-- 索引
CREATE INDEX idx_platform_admin_audit_log_admin_user_id ON platform_admin_audit_log(admin_user_id);
CREATE INDEX idx_platform_admin_audit_log_target_entity ON platform_admin_audit_log(target_entity_type, target_entity_id);
-- CREATE INDEX idx_admin_review_queue_status ON admin_review_queue(status);
```


## Analytics Service Database
```sql
-- Analytics Service Database

-- 每日使用者活動快照 (範例)
CREATE TABLE daily_user_activity_summary (
    summary_date DATE PRIMARY KEY,
    active_users_count INT,          -- 活躍使用者數量
    new_registrations_count INT,
    -- ... 其他使用者相關指標
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 每日內容互動快照 (範例)
CREATE TABLE daily_content_summary (
    summary_date DATE PRIMARY KEY,
    articles_published_count INT,
    questions_asked_count INT,
    comments_made_count INT,
    answers_given_count INT,
    -- 其他指標
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 企業活動概覽
CREATE TABLE organization_activity_overview (
    organization_id UUID,
    summary_date DATE,
    active_users_in_org_count INT,
    articles_posted_by_org_users_count INT,
    activities_created_count INT,
    PRIMARY KEY (organization_id, summary_date),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 儀表板用：最近的文章/問題 (這更像是一個快取或投影，資料源是 Content Service)
CREATE TABLE recent_dashboard_items (
    item_id UUID PRIMARY KEY, -- article_id 或 question_id
    item_type VARCHAR(20) NOT NULL, -- 'article', 'question'
    title VARCHAR(255),
    author_display_name VARCHAR(100), -- 冗餘顯示用
    organization_display_name VARCHAR(200), -- 冗餘顯示用
    created_or_published_at TIMESTAMP WITH TIME ZONE,
    -- 用於排序和快速提取
    display_priority INT DEFAULT 0,
    data_payload JSONB, -- 原始項目摘要，方便前端直接使用
    refreshed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 索引 (針對查詢優化)
CREATE INDEX idx_daily_user_activity_summary_date ON daily_user_activity_summary(summary_date);
CREATE INDEX idx_daily_content_summary_date ON daily_content_summary(summary_date);
CREATE INDEX idx_organization_activity_overview_org_date ON organization_activity_overview(organization_id, summary_date);
CREATE INDEX idx_recent_dashboard_items_type_date ON recent_dashboard_items(item_type, created_or_published_at DESC);
```

## Content Service Database
```sql
-- Content Service Database

-- 內容審核狀態枚舉
CREATE TYPE content_status_enum AS ENUM (
    'draft',            -- 草稿 (若有此功能)
    'pending_review',   -- 等待審核 (SDGs/ESG 相關性)
    'published',        -- 已發布
    'rejected',         -- 審核未通過
    'archived'          -- 已封存/下架
);

-- 文章表 (經驗分享文章)
CREATE TABLE articles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL, -- 主要文字內容

    -- 作者資訊 (從 Member Service 獲取)
    author_user_id UUID NOT NULL, -- 必須是 'organization_user'
    author_organization_id UUID,  -- 作者所屬企業 ID (方便查詢)

    status content_status_enum NOT NULL DEFAULT 'pending_review',
    rejection_reason TEXT, -- 審核未通過原因

    -- 圖片與影片連結 (使用 JSONB 儲存陣列，或另開表)
    -- 範例：[{ "type": "image", "url": "...", "caption": "..." }, { "type": "video", "url": "...", "provider": "youtube" }]
    media_attachments JSONB,

    view_count INT DEFAULT 0,
    like_count INT DEFAULT 0, -- 若有按讚功能

    published_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- tags
CREATE TABLE tags (
    id SERIAL PRIMARY KEY, -- 使用 SERIAL 或 UUID 皆可
    name VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 文章與標籤關聯表 (多對多)
CREATE TABLE article_tags (
    article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
    tag_id INT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY (article_id, tag_id)
);

-- 文章留言表
CREATE TABLE article_comments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
    commenter_user_id UUID NOT NULL, -- 任何已登入使用者皆可留言
    parent_comment_id UUID REFERENCES article_comments(id) ON DELETE CASCADE, -- 用於巢狀留言
    content VARCHAR(100) NOT NULL, -- 文字上限 100 字

    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 問題討論串表 (發問區)
CREATE TABLE questions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title VARCHAR(255) NOT NULL, -- 問題標題/簡述
    details TEXT,                -- 問題詳細描述 (純文字)

    asker_user_id UUID NOT NULL, -- 任何已登入使用者皆可提問
    asker_organization_id UUID,  -- 提問者所屬企業ID (若適用)

    status content_status_enum NOT NULL DEFAULT 'pending_review', -- 問題也需審核 SDGs/ESG 相關性
    rejection_reason TEXT,

    view_count INT DEFAULT 0,
    answer_count INT DEFAULT 0, -- 回答數量

    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -- 最後回答或編輯時間
);

-- 問題回答表
CREATE TABLE Youtubes ( -- *** 原 Youtubes 表已更名 ***
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    question_id UUID NOT NULL REFERENCES questions(id) ON DELETE CASCADE,
    answerer_user_id UUID NOT NULL, -- 任何已登入使用者皆可回答
    content TEXT NOT NULL,          -- 純文字回答

    is_accepted_answer BOOLEAN DEFAULT FALSE, -- 若有最佳答案功能

    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 索引
CREATE INDEX idx_articles_author_user_id ON articles(author_user_id);
CREATE INDEX idx_articles_status ON articles(status);
CREATE INDEX idx_article_tags_tag_id ON article_tags(tag_id);
CREATE INDEX idx_article_comments_article_id ON article_comments(article_id);
CREATE INDEX idx_questions_asker_user_id ON questions(asker_user_id);
CREATE INDEX idx_questions_status ON questions(status);
CREATE INDEX idx_Youtubes_question_id ON Youtubes(question_id); -- *** 索引名稱與對應表已更新 ***

-- 全文搜索相關的索引 (依賴具體資料庫，例如 PostgreSQL 的 GIN/GIST 索引)
-- CREATE INDEX idx_articles_content_search ON articles USING gin(to_tsvector('chinese', title || ' ' || content));
-- CREATE INDEX idx_questions_content_search ON questions USING gin(to_tsvector('chinese', title || ' ' || details));
-- CREATE INDEX idx_tags_name_search ON tags USING gin(to_tsvector('chinese', name));
```

## Member Service Database
```sql
-- Member Service Database

-- 使用者身份枚舉
CREATE TYPE member_role_enum AS ENUM (
    'platform_admin',  -- 平台管理者
    'general_user',    -- 一般使用者
    'organization_admin', -- 企業後臺管理者
    'organization_user'   -- 企業使用者
);

-- email驗證枚舉
CREATE TYPE email_verification_enum AS ENUM (
    'email_verification',
    'password_reset',
);

-- 使用者帳號表
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL, -- 使用者帳號名稱
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    full_name VARCHAR(100),              -- 真實姓名或暱稱
    avatar_url VARCHAR(255),             -- 頭像圖片URL
    bio TEXT,                            -- 個人簡介

    role member_role_enum NOT NULL DEFAULT 'general_user', -- 使用者身份

    -- 關聯到 Organization Service 的 organization_id
    -- 對於 platform_admin 和 general_user，此欄位為 NULL
    -- 對於 organization_admin 和 organization_user，此欄位必要
    organization_id UUID,
    -- 冗餘企業名稱，方便 Member Service 內部快速查詢，但主要來源是 Organization Service
    organization_name VARCHAR(200),

    is_email_verified BOOLEAN DEFAULT FALSE, -- Email 認證了沒
    is_active BOOLEAN DEFAULT TRUE,          -- 帳號是否啟用 (用於軟刪除或禁用)
    status_reason TEXT,                      -- 帳號狀態變更原因（要？）

    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    last_login_at TIMESTAMP WITH TIME ZONE
);

-- 使用者 Email 驗證碼/密碼重設 Token 錶
CREATE TABLE user_verifications (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    verification_type VARCHAR(20) NOT NULL, -- 'email_verification', 'password_reset'
    token VARCHAR(100) UNIQUE NOT NULL,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    is_used BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 使用者會話表 (用於管理登入狀態，可選，取決於認證方案)
CREATE TABLE user_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    session_token VARCHAR(255) UNIQUE NOT NULL,
    refresh_token VARCHAR(255) UNIQUE,
    ip_address INET,
    user_agent TEXT,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 使用者通知設定表
CREATE TABLE user_notification_settings (
    user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
    email_notifications_enabled BOOLEAN DEFAULT TRUE,    -- 不累嘎總開關：是否接收郵件通知
    -- 可以再細分更多通知類型，例如：
    -- new_post_in_followed_topic BOOLEAN DEFAULT TRUE,
    -- new_reply_to_my_comment BOOLEAN DEFAULT TRUE,
    -- activity_updates BOOLEAN DEFAULT TRUE, -- 來自活動模組的更新
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_users_organization_id ON users(organization_id) WHERE organization_id IS NOT NULL;
CREATE INDEX idx_user_verifications_token ON user_verifications(token);
CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
```

## Organization Service Database
```sql
-- Organization Service Database

-- 企業帳號申請狀態枚舉
CREATE TYPE organization_status_enum AS ENUM (
    'pending_approval', -- 等待平台管理者審核
    'approved',         -- 已核准
    'rejected',         -- 已拒絕
    'active',           -- 活躍 (核准後)
    'suspended'         -- 已暫停
);

-- 系統版本枚舉 (免費版/付費版)
CREATE TYPE system_version_enum AS ENUM (
    'free',  -- 免費版
    'paid'   -- 付費版
);

-- 企業表
CREATE TABLE organizations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(200) UNIQUE NOT NULL, -- 企業名稱
    -- 企業後台管理者 (對應到 Member Service 的 user_id)
    -- 此 user_id 的 role 應為 'organization_admin'
    admin_user_id UUID UNIQUE, 
    status organization_status_enum NOT NULL DEFAULT 'pending_approval',
    status_reason TEXT,                      -- 狀態變更原因

    -- 新增：企業基本資訊欄位
    industry VARCHAR(100),                   -- 行業別
    website VARCHAR(255),                    -- 企業網站
    description TEXT,                        -- 企業描述

    current_version system_version_enum NOT NULL DEFAULT 'free',
    subscription_start_date TIMESTAMP WITH TIME ZONE,
    subscription_end_date TIMESTAMP WITH TIME ZONE, -- 付費版的到期日

    -- 其他企業相關資訊 (可擴充)
    -- address TEXT,
    -- contact_phone VARCHAR(30),

    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    approved_by UUID, -- 平台管理者 User ID (如果需要記錄是誰核准的)
    approved_at TIMESTAMP WITH TIME ZONE
);

-- 企業成員表 (用於管理企業使用者與企業的關係)
CREATE TABLE organization_members (
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    -- 對應到 Member Service 的 user_id
    -- 此 user_id 的 role 應為 'organization_user' 或 'organization_admin'
    user_id UUID NOT NULL,
    member_status organization_status_enum NOT NULL DEFAULT 'pending_approval', -- 企業使用者加入企業的審核狀態
    status_reason TEXT, -- 審核不過原因等

    -- 企業內部細粒度權限 (範例，可擴展)
    can_post_articles BOOLEAN DEFAULT FALSE,   -- 是否能發布經驗分享文章 (由企業後台管理者控制)
    can_edit_activities BOOLEAN DEFAULT FALSE, -- 是否能編輯活動資料 (由企業後台管理者控制)

    -- 移除 can_ask_questions 欄位，因為提問應該是所有用戶的基本權限
    -- can_manage_specific_activity_id UUID, -- 管理特定活動的權限

    joined_at TIMESTAMP WITH TIME ZONE, -- 審核通過加入時間
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- 申請時間
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (organization_id, user_id)
);

-- 索引
CREATE INDEX idx_organizations_name ON organizations(name);
CREATE INDEX idx_organizations_admin_user_id ON organizations(admin_user_id);
CREATE INDEX idx_organizations_status ON organizations(status);
CREATE INDEX idx_organizations_industry ON organizations(industry);
CREATE INDEX idx_organization_members_user_id ON organization_members(user_id);
```

## 怎麼起在本地
路徑要改
### docker-compose.yml
```
version: '3.8'

services:
  postgres_db:
    image: postgres:15
    container_name: impact_copilot_postgres_demo
    environment:
      POSTGRES_USER: ricky_user
      POSTGRES_PASSWORD: ricky_secret
      POSTGRES_DB: main_db # 這個資料庫會被自動建立，init-script.sh 會用它來連線並建立其他DB
    ports:
      - "5433:5432"
    volumes:
      - pg_data_demo:/var/lib/postgresql/data
      - ../db-v1:/docker-entrypoint-initdb.d/schemas
      - ./init-db/init-script.sh:/docker-entrypoint-initdb.d/init-script.sh
    restart: unless-stopped
    healthcheck: 
      test: ["CMD-SHELL", "pg_isready -U ricky_user -d main_db"]
      interval: 10s
      timeout: 5s
      retries: 5

  pgadmin:
    image: dpage/pgadmin4
    container_name: impact_copilot_pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: ricky.chen@infinirc.com # pgAdmin 登入 Email
      PGADMIN_DEFAULT_PASSWORD: admin          # pgAdmin 登入密碼
    ports:
      - "5050:80" # pgAdmin on http://localhost:5050
    volumes:
      - pgadmin_data:/var/lib/pgadmin 
    depends_on:
      postgres_db: 
        condition: service_healthy 
    restart: unless-stopped

volumes:
  pg_data_demo:
  pgadmin_data:
```

### demo-api/init-db/init-script.sh
```
#!/bin/bash
set -e 


psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE DATABASE member_service_db;
    CREATE DATABASE organization_service_db;
    CREATE DATABASE content_service_db;
    CREATE DATABASE activity_service_db;
EOSQL

echo "資料庫 member_service_db, organization_service_db, content_service_db, activity_service_db 已建立。"

# 匯入 Schema
echo "Member Service Schema..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "member_service_db" < /docker-entrypoint-initdb.d/schemas/Member-Service-Database.sql

echo " Organization Service Schema..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "organization_service_db" < /docker-entrypoint-initdb.d/schemas/Organization-Service-Database.sql

echo " Content Service Schema..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "content_service_db" < /docker-entrypoint-initdb.d/schemas/Content-Service-Database.sql

echo " Activity Service Schema..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "activity_service_db" < /docker-entrypoint-initdb.d/schemas/Activity-Service-Database.sql

echo "應該成了"
```