from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from ..database import get_db from ..models import Consumer, Event, ParseJob from ..schemas import ( AnalyticsSummary, ConsumerCreate, ConsumerRead, ConsumerUpdate, EventListResponse, EventRead, ParseJobCreate, ParseJobRead, TimelinePoint, TopItem, ) from ..services.analytics import ( get_analytics_summary, get_timeline, get_top_localities, get_top_regions, ) from ..services.events_query import build_events_query from ..services.filtering import ( consumer_to_read, create_consumer, list_consumers, rotate_consumer_key, update_consumer, ) from ..services.jobs import enqueue_job router = APIRouter(prefix="/admin", tags=["admin"]) @router.post("/jobs", response_model=ParseJobRead, status_code=201) def create_parse_job(payload: ParseJobCreate, db: Session = Depends(get_db)): job = ParseJob( source_type=payload.source_type, source_config=payload.source_config, schedule=payload.schedule, status="queued", ) db.add(job) db.commit() db.refresh(job) enqueue_job(job.id, job.source_type, job.source_config) return job @router.get("/jobs", response_model=list[ParseJobRead]) def list_parse_jobs(db: Session = Depends(get_db)): return db.query(ParseJob).order_by(ParseJob.id.desc()).all() @router.get("/jobs/{job_id}", response_model=ParseJobRead) def get_parse_job(job_id: int, db: Session = Depends(get_db)): job = db.query(ParseJob).filter(ParseJob.id == job_id).first() if not job: raise HTTPException(status_code=404, detail="Job not found") return job @router.post("/jobs/{job_id}/retry", response_model=ParseJobRead) def retry_parse_job(job_id: int, db: Session = Depends(get_db)): job = db.query(ParseJob).filter(ParseJob.id == job_id).first() if not job: raise HTTPException(status_code=404, detail="Job not found") job.status = "queued" job.last_error = None db.commit() db.refresh(job) enqueue_job(job.id, job.source_type, job.source_config) return job @router.get("/events", response_model=EventListResponse) def list_events( limit: int = Query(default=50, ge=1, le=1000), offset: int = Query(default=0, ge=0), source_type: str | None = None, region: str | None = None, topic: str | None = None, locality: str | None = None, date_from: datetime | None = None, date_to: datetime | None = None, search: str | None = None, db: Session = Depends(get_db), ): query = build_events_query( db, source_type=source_type, region=region, topic=topic, locality=locality, date_from=date_from, date_to=date_to, search=search, ) total = query.count() items = query.offset(offset).limit(limit).all() return EventListResponse(items=items, total=total) @router.get("/analytics/summary", response_model=AnalyticsSummary) def analytics_summary(db: Session = Depends(get_db)): return get_analytics_summary(db) @router.get("/analytics/timeline", response_model=list[TimelinePoint]) def analytics_timeline( days: int = Query(default=30, ge=1, le=365), db: Session = Depends(get_db), ): return get_timeline(db, days=days) @router.get("/analytics/top-localities", response_model=list[TopItem]) def analytics_top_localities( limit: int = Query(default=10, ge=1, le=100), db: Session = Depends(get_db), ): return get_top_localities(db, limit=limit) @router.get("/analytics/top-regions", response_model=list[TopItem]) def analytics_top_regions( limit: int = Query(default=10, ge=1, le=100), db: Session = Depends(get_db), ): return get_top_regions(db, limit=limit) @router.get("/consumers", response_model=list[ConsumerRead]) def list_pi_consumers(db: Session = Depends(get_db)): return [consumer_to_read(c) for c in list_consumers(db)] @router.post("/consumers", response_model=ConsumerRead, status_code=201) def create_pi_consumer(payload: ConsumerCreate, db: Session = Depends(get_db)): consumer, api_key = create_consumer( db, name=payload.name, regions=payload.regions, topics=payload.topics, date_from=payload.date_from, ) return consumer_to_read(consumer, api_key=api_key) @router.patch("/consumers/{consumer_id}", response_model=ConsumerRead) def patch_pi_consumer( consumer_id: int, payload: ConsumerUpdate, db: Session = Depends(get_db), ): consumer = db.query(Consumer).filter(Consumer.id == consumer_id).first() if not consumer: raise HTTPException(status_code=404, detail="Consumer not found") consumer = update_consumer( db, consumer, name=payload.name, is_active=payload.is_active, regions=payload.regions, topics=payload.topics, date_from=payload.date_from, ) return consumer_to_read(consumer) @router.post("/consumers/{consumer_id}/rotate-key", response_model=ConsumerRead) def rotate_pi_consumer_key(consumer_id: int, db: Session = Depends(get_db)): consumer = db.query(Consumer).filter(Consumer.id == consumer_id).first() if not consumer: raise HTTPException(status_code=404, detail="Consumer not found") consumer, api_key = rotate_consumer_key(db, consumer) return consumer_to_read(consumer, api_key=api_key)