mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-24 15:48:11 +02:00
feat: Implement API endpoints for retrieving enseignes, paginated catalogues, detailed catalogue information, catalogue pages, and scraping statistics.
This commit is contained in:
1 parent
237e4a2adf
commit
09a647a437
1 file changed
+121
-1
+121
-1
@@ -9,9 +9,11 @@ from datetime import datetime
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func, and_
|
from sqlalchemy import func, and_
|
||||||
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models import Enseigne, Catalogue, CataloguePage, ScrapingLog
|
from app.models import Enseigne, Catalogue, CataloguePage, ScrapingLog, User
|
||||||
|
from app.dependencies import get_current_user
|
||||||
from app.schemas_catalogues import (
|
from app.schemas_catalogues import (
|
||||||
EnseigneResponse,
|
EnseigneResponse,
|
||||||
CatalogueListResponse,
|
CatalogueListResponse,
|
||||||
@@ -167,6 +169,124 @@ async def get_catalogue_detail(
|
|||||||
if not catalogue:
|
if not catalogue:
|
||||||
raise HTTPException(status_code=404, detail="Catalogue not found")
|
raise HTTPException(status_code=404, detail="Catalogue not found")
|
||||||
|
|
||||||
|
return CatalogueDetailResponse.model_validate(catalogue)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{catalogue_id}/pages", response_model=list[CataloguePageResponse])
|
||||||
|
async def get_catalogue_pages(
|
||||||
|
catalogue_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Get all pages for a specific catalogue"""
|
||||||
|
catalogue = db.query(Catalogue).filter(Catalogue.id == catalogue_id).first()
|
||||||
|
|
||||||
|
if not catalogue:
|
||||||
|
raise HTTPException(status_code=404, detail="Catalogue not found")
|
||||||
|
|
||||||
|
pages = (
|
||||||
|
db.query(CataloguePage)
|
||||||
|
.filter(CataloguePage.catalogue_id == catalogue_id)
|
||||||
|
.order_by(CataloguePage.numero_page)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
return [CataloguePageResponse.model_validate(page) for page in pages]
|
||||||
|
|
||||||
|
|
||||||
|
# === Admin Endpoints ===
|
||||||
|
|
||||||
|
@router.get("/admin/stats", response_model=ScrapingStatsResponse)
|
||||||
|
async def get_scraping_stats(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Get scraping statistics"""
|
||||||
|
total_catalogues = db.query(func.count(Catalogue.id)).scalar()
|
||||||
|
|
||||||
|
# Catalogues par enseigne
|
||||||
|
catalogues_par_enseigne = {}
|
||||||
|
enseignes = db.query(Enseigne).all()
|
||||||
|
for enseigne in enseignes:
|
||||||
|
count = (
|
||||||
|
db.query(func.count(Catalogue.id))
|
||||||
|
.filter(Catalogue.enseigne_id == enseigne.id)
|
||||||
|
.scalar()
|
||||||
|
)
|
||||||
|
catalogues_par_enseigne[enseigne.nom] = count
|
||||||
|
|
||||||
|
# Last scraping
|
||||||
|
last_scraping = (
|
||||||
|
db.query(ScrapingLog)
|
||||||
|
.order_by(ScrapingLog.date_execution.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
return ScrapingStatsResponse(
|
||||||
|
total_catalogues=total_catalogues,
|
||||||
|
catalogues_par_enseigne=catalogues_par_enseigne,
|
||||||
|
derniere_mise_a_jour=last_scraping.date_execution if last_scraping else None,
|
||||||
|
prochaine_execution="6h00 et 18h00",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/admin/scraping/logs", response_model=list[ScrapingLogResponse])
|
||||||
|
async def get_scraping_logs(
|
||||||
|
limit: int = Query(20, ge=1, le=100),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Get scraping logs"""
|
||||||
|
logs = (
|
||||||
|
db.query(ScrapingLog)
|
||||||
|
.order_by(ScrapingLog.date_execution.desc())
|
||||||
|
.limit(limit)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
return [ScrapingLogResponse.model_validate(log) for log in logs]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/admin/scraping/trigger")
|
||||||
|
async def trigger_scraping(
|
||||||
|
enseigne_id: int | None = Query(None, description="Optional: specific enseigne ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Trigger manual scraping"""
|
||||||
|
if enseigne_id:
|
||||||
|
enseigne = db.query(Enseigne).filter(Enseigne.id == enseigne_id).first()
|
||||||
|
if not enseigne:
|
||||||
|
raise HTTPException(status_code=404, detail="Enseigne not found")
|
||||||
|
|
||||||
|
async with async_playwright() as p:
|
||||||
|
import os
|
||||||
|
browserless_url = os.environ.get("BROWSERLESS_URL", "ws://browserless:3000")
|
||||||
|
browser = await p.chromium.connect_over_cdp(browserless_url)
|
||||||
|
log = await scrape_enseigne(enseigne, db, browser)
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"Scraping completed for {enseigne.nom}",
|
||||||
|
"catalogues_trouves": log.catalogues_trouves,
|
||||||
|
"catalogues_nouveaux": log.catalogues_nouveaux,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logs = await scrape_all_enseignes(db)
|
||||||
|
total_nouveaux = sum(log.catalogues_nouveaux for log in logs)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"Scraping completed for all enseignes",
|
||||||
|
"catalogues_nouveaux": total_nouveaux,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/admin/cleanup")
|
||||||
|
async def cleanup_catalogs(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Delete invalid catalogs (0 pages or generic titles)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
raise HTTPException(status_code=403, detail="Not authorized")
|
raise HTTPException(status_code=403, detail="Not authorized")
|
||||||
|
|
||||||
deleted_count = 0
|
deleted_count = 0
|
||||||
|
|||||||
Reference in new issue
Block a user