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>
This commit is contained in:
239
app/api/v1/endpoints/groups.py
Normal file
239
app/api/v1/endpoints/groups.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""
|
||||
群組管理 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()
|
||||
Reference in New Issue
Block a user