企業內部新聞彙整與分析系統 - 自動新聞抓取 (Digitimes, 經濟日報, 工商時報) - AI 智慧摘要 (OpenAI/Claude/Ollama) - 群組管理與訂閱通知 - 已清理 Python 快取檔案 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
240 lines
7.3 KiB
Python
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()
|