""" 群組管理 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()