Files
daily-news-app/app/api/v1/endpoints/groups.py
donald db0f0bbfe7 Initial commit: Daily News App
企業內部新聞彙整與分析系統
- 自動新聞抓取 (Digitimes, 經濟日報, 工商時報)
- AI 智慧摘要 (OpenAI/Claude/Ollama)
- 群組管理與訂閱通知
- 已清理 Python 快取檔案

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 23:53:24 +08:00

240 lines
7.3 KiB
Python

"""
群組管理 API 端點
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.db.session import get_db
from app.models import User, Group, Keyword, Subscription
from app.schemas.group import (
GroupCreate, GroupUpdate, GroupResponse, GroupDetailResponse,
GroupListResponse, KeywordCreate, KeywordResponse
)
from app.schemas.user import PaginationResponse
from app.api.v1.endpoints.auth import get_current_user, require_roles
router = APIRouter()
@router.get("", response_model=GroupListResponse)
def list_groups(
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
category: Optional[str] = None,
active_only: bool = True,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""取得群組列表"""
query = db.query(Group)
if category:
query = query.filter(Group.category == category)
if active_only:
query = query.filter(Group.is_active == True)
total = query.count()
groups = query.offset((page - 1) * limit).limit(limit).all()
# 計算關鍵字數和訂閱數
result = []
for g in groups:
keyword_count = db.query(Keyword).filter(Keyword.group_id == g.id).count()
subscriber_count = db.query(Subscription).filter(Subscription.group_id == g.id).count()
group_dict = {
"id": g.id,
"name": g.name,
"description": g.description,
"category": g.category.value,
"is_active": g.is_active,
"keyword_count": keyword_count,
"subscriber_count": subscriber_count
}
result.append(GroupResponse(**group_dict))
return GroupListResponse(
data=result,
pagination=PaginationResponse(
page=page, limit=limit, total=total,
total_pages=(total + limit - 1) // limit
)
)
@router.post("", response_model=GroupResponse, status_code=status.HTTP_201_CREATED)
def create_group(
group_in: GroupCreate,
db: Session = Depends(get_db),
current_user: User = Depends(require_roles("admin", "editor"))
):
"""新增群組"""
group = Group(
name=group_in.name,
description=group_in.description,
category=group_in.category,
ai_background=group_in.ai_background,
ai_prompt=group_in.ai_prompt,
created_by=current_user.id
)
db.add(group)
db.commit()
db.refresh(group)
# 新增關鍵字
if group_in.keywords:
for kw in group_in.keywords:
keyword = Keyword(group_id=group.id, keyword=kw)
db.add(keyword)
db.commit()
return GroupResponse(
id=group.id,
name=group.name,
description=group.description,
category=group.category.value,
is_active=group.is_active,
keyword_count=len(group_in.keywords) if group_in.keywords else 0,
subscriber_count=0
)
@router.get("/{group_id}", response_model=GroupDetailResponse)
def get_group(
group_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""取得群組詳情"""
group = db.query(Group).filter(Group.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="群組不存在")
keywords = db.query(Keyword).filter(Keyword.group_id == group_id).all()
keyword_count = len(keywords)
subscriber_count = db.query(Subscription).filter(Subscription.group_id == group_id).count()
return GroupDetailResponse(
id=group.id,
name=group.name,
description=group.description,
category=group.category.value,
is_active=group.is_active,
ai_background=group.ai_background,
ai_prompt=group.ai_prompt,
keywords=[KeywordResponse.model_validate(k) for k in keywords],
keyword_count=keyword_count,
subscriber_count=subscriber_count,
created_at=group.created_at,
updated_at=group.updated_at
)
@router.put("/{group_id}", response_model=GroupResponse)
def update_group(
group_id: int,
group_in: GroupUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(require_roles("admin", "editor"))
):
"""更新群組"""
group = db.query(Group).filter(Group.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="群組不存在")
for field, value in group_in.model_dump(exclude_unset=True).items():
setattr(group, field, value)
db.commit()
db.refresh(group)
keyword_count = db.query(Keyword).filter(Keyword.group_id == group_id).count()
subscriber_count = db.query(Subscription).filter(Subscription.group_id == group_id).count()
return GroupResponse(
id=group.id,
name=group.name,
description=group.description,
category=group.category.value,
is_active=group.is_active,
keyword_count=keyword_count,
subscriber_count=subscriber_count
)
@router.delete("/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_group(
group_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_roles("admin"))
):
"""刪除群組"""
group = db.query(Group).filter(Group.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="群組不存在")
db.delete(group)
db.commit()
# ===== 關鍵字管理 =====
@router.get("/{group_id}/keywords", response_model=list[KeywordResponse])
def list_keywords(
group_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""取得群組關鍵字"""
keywords = db.query(Keyword).filter(Keyword.group_id == group_id).all()
return [KeywordResponse.model_validate(k) for k in keywords]
@router.post("/{group_id}/keywords", response_model=KeywordResponse, status_code=status.HTTP_201_CREATED)
def add_keyword(
group_id: int,
keyword_in: KeywordCreate,
db: Session = Depends(get_db),
current_user: User = Depends(require_roles("admin", "editor"))
):
"""新增關鍵字"""
group = db.query(Group).filter(Group.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="群組不存在")
# 檢查重複
existing = db.query(Keyword).filter(
Keyword.group_id == group_id,
Keyword.keyword == keyword_in.keyword
).first()
if existing:
raise HTTPException(status_code=400, detail="關鍵字已存在")
keyword = Keyword(group_id=group_id, keyword=keyword_in.keyword)
db.add(keyword)
db.commit()
db.refresh(keyword)
return keyword
@router.delete("/{group_id}/keywords/{keyword_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_keyword(
group_id: int,
keyword_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_roles("admin", "editor"))
):
"""刪除關鍵字"""
keyword = db.query(Keyword).filter(
Keyword.id == keyword_id,
Keyword.group_id == group_id
).first()
if not keyword:
raise HTTPException(status_code=404, detail="關鍵字不存在")
db.delete(keyword)
db.commit()