Files
site11/services/news-engine-console/backend/app/api/keywords.py
jungwoo choi 07088e60e9 feat: Implement backend core functionality for news-engine-console
Phase 1 Backend Implementation:
-  MongoDB data models (Keyword, Pipeline, User, Application)
-  Pydantic schemas for all models with validation
-  KeywordService: Full CRUD, filtering, pagination, stats, toggle status
-  PipelineService: Full CRUD, start/stop/restart, logs, config management
-  Keywords API: 8 endpoints with complete functionality
-  Pipelines API: 11 endpoints with complete functionality
-  Updated TODO.md to reflect completion

Key Features:
- Async MongoDB operations with Motor
- Comprehensive filtering and pagination support
- Pipeline logging system
- Statistics tracking for keywords and pipelines
- Proper error handling with HTTP status codes
- Type-safe request/response models

Files Added:
- models/: 4 data models with PyObjectId support
- schemas/: 4 schema modules with Create/Update/Response patterns
- services/: KeywordService (234 lines) + PipelineService (332 lines)

Files Modified:
- api/keywords.py: 40 → 212 lines (complete implementation)
- api/pipelines.py: 25 → 300 lines (complete implementation)
- TODO.md: Updated checklist with completed items

Next Steps:
- UserService with authentication
- ApplicationService for OAuth2
- MonitoringService
- Redis integration
- Frontend implementation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 16:24:14 +09:00

212 lines
6.8 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query, status
from typing import Optional
from app.core.auth import get_current_active_user, User
from app.core.database import get_database
from app.services.keyword_service import KeywordService
from app.schemas.keyword import (
KeywordCreate,
KeywordUpdate,
KeywordResponse,
KeywordListResponse,
KeywordStats
)
router = APIRouter()
def get_keyword_service(db=Depends(get_database)) -> KeywordService:
"""Dependency to get keyword service"""
return KeywordService(db)
@router.get("/", response_model=KeywordListResponse)
async def get_keywords(
category: Optional[str] = Query(None, description="Filter by category"),
status: Optional[str] = Query(None, description="Filter by status (active/inactive)"),
search: Optional[str] = Query(None, description="Search in keyword text"),
page: int = Query(1, ge=1, description="Page number"),
page_size: int = Query(50, ge=1, le=100, description="Items per page"),
sort_by: str = Query("created_at", description="Field to sort by"),
sort_order: int = Query(-1, ge=-1, le=1, description="1 for ascending, -1 for descending"),
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Get all keywords with filtering, pagination, and sorting"""
keywords, total = await keyword_service.get_keywords(
category=category,
status=status,
search=search,
page=page,
page_size=page_size,
sort_by=sort_by,
sort_order=sort_order
)
# Convert to response models
keyword_responses = [
KeywordResponse(
_id=str(kw.id),
keyword=kw.keyword,
category=kw.category,
status=kw.status,
pipeline_type=kw.pipeline_type,
priority=kw.priority,
metadata=kw.metadata,
created_at=kw.created_at,
updated_at=kw.updated_at,
created_by=kw.created_by
)
for kw in keywords
]
return KeywordListResponse(
keywords=keyword_responses,
total=total,
page=page,
page_size=page_size
)
@router.get("/{keyword_id}", response_model=KeywordResponse)
async def get_keyword(
keyword_id: str,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Get a keyword by ID"""
keyword = await keyword_service.get_keyword_by_id(keyword_id)
if not keyword:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Keyword with ID {keyword_id} not found"
)
return KeywordResponse(
_id=str(keyword.id),
keyword=keyword.keyword,
category=keyword.category,
status=keyword.status,
pipeline_type=keyword.pipeline_type,
priority=keyword.priority,
metadata=keyword.metadata,
created_at=keyword.created_at,
updated_at=keyword.updated_at,
created_by=keyword.created_by
)
@router.post("/", response_model=KeywordResponse, status_code=status.HTTP_201_CREATED)
async def create_keyword(
keyword_data: KeywordCreate,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Create new keyword"""
keyword = await keyword_service.create_keyword(
keyword_data=keyword_data,
created_by=current_user.username
)
return KeywordResponse(
_id=str(keyword.id),
keyword=keyword.keyword,
category=keyword.category,
status=keyword.status,
pipeline_type=keyword.pipeline_type,
priority=keyword.priority,
metadata=keyword.metadata,
created_at=keyword.created_at,
updated_at=keyword.updated_at,
created_by=keyword.created_by
)
@router.put("/{keyword_id}", response_model=KeywordResponse)
async def update_keyword(
keyword_id: str,
keyword_data: KeywordUpdate,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Update keyword"""
keyword = await keyword_service.update_keyword(keyword_id, keyword_data)
if not keyword:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Keyword with ID {keyword_id} not found"
)
return KeywordResponse(
_id=str(keyword.id),
keyword=keyword.keyword,
category=keyword.category,
status=keyword.status,
pipeline_type=keyword.pipeline_type,
priority=keyword.priority,
metadata=keyword.metadata,
created_at=keyword.created_at,
updated_at=keyword.updated_at,
created_by=keyword.created_by
)
@router.delete("/{keyword_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_keyword(
keyword_id: str,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Delete keyword"""
success = await keyword_service.delete_keyword(keyword_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Keyword with ID {keyword_id} not found"
)
return None
@router.post("/{keyword_id}/toggle", response_model=KeywordResponse)
async def toggle_keyword_status(
keyword_id: str,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Toggle keyword status between active and inactive"""
keyword = await keyword_service.toggle_keyword_status(keyword_id)
if not keyword:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Keyword with ID {keyword_id} not found"
)
return KeywordResponse(
_id=str(keyword.id),
keyword=keyword.keyword,
category=keyword.category,
status=keyword.status,
pipeline_type=keyword.pipeline_type,
priority=keyword.priority,
metadata=keyword.metadata,
created_at=keyword.created_at,
updated_at=keyword.updated_at,
created_by=keyword.created_by
)
@router.get("/{keyword_id}/stats", response_model=KeywordStats)
async def get_keyword_stats(
keyword_id: str,
current_user: User = Depends(get_current_active_user),
keyword_service: KeywordService = Depends(get_keyword_service)
):
"""Get statistics for a keyword"""
stats = await keyword_service.get_keyword_stats(keyword_id)
if not stats:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Keyword with ID {keyword_id} not found"
)
return stats