Files
Priceflow/app/schemas.py
T
Claude 1116c3a648 feat: Add multi-site product search system with SearXNG integration
This commit adds a comprehensive product search feature that allows users
to search for products across multiple configured e-commerce sites.

Backend changes:
- Add SearXNG service to docker-compose for meta-search capability
- Add SearchSite model and Alembic migration with default sites
- Create searxng_service.py for search queries across sites
- Create light_scraper_service.py for fast HTTP-based extraction
- Create search_service.py orchestrator combining SearXNG + HTTP + Browserless
- Add API endpoints for search (/api/search) with SSE streaming
- Add CRUD endpoints for search sites (/api/search-sites)

Frontend changes:
- Add new /search page with real-time results display
- Create SearchResultCard component for product display
- Add Progress, Checkbox, and Badge UI components
- Update Settings page with search sites management section
- Add navigation link to search page
- Create new modern SVG logo with price-flow gradient design
- Update favicon to SVG format

The search system uses a hybrid approach:
1. SearXNG finds product URLs across configured sites
2. Light HTTP scraper attempts fast extraction
3. Falls back to Browserless for JavaScript-heavy sites
4. Results stream progressively via SSE
2025-11-22 11:03:48 +00:00

121 lines
2.9 KiB
Python

from datetime import datetime
from pydantic import BaseModel, ConfigDict
class NotificationProfileCreate(BaseModel):
name: str
apprise_url: str
notify_on_price_drop: bool = True
notify_on_target_price: bool = True
price_drop_threshold_percent: float = 10.0
notify_on_stock_change: bool = True
check_interval_minutes: int = 60
class NotificationProfileUpdate(NotificationProfileCreate):
pass
class NotificationProfileResponse(NotificationProfileCreate):
id: int
model_config = ConfigDict(from_attributes=True)
class ItemCreate(BaseModel):
url: str
name: str
selector: str | None = None
target_price: float | None = None
check_interval_minutes: int = 60
tags: str | None = None
description: str | None = None
notification_profile_id: int | None = None
class ItemResponse(ItemCreate):
id: int
current_price: float | None
in_stock: bool | None
current_price_confidence: float | None = None
in_stock_confidence: float | None = None
is_active: bool
last_checked: datetime | None
is_refreshing: bool = False
last_error: str | None = None
screenshot_url: str | None = None
model_config = ConfigDict(from_attributes=True)
class SettingsUpdate(BaseModel):
key: str
value: str
# === Search Sites Schemas ===
class SearchSiteCreate(BaseModel):
name: str
domain: str
logo_url: str | None = None
category: str | None = None
is_active: bool = True
priority: int = 0
requires_js: bool = False
class SearchSiteUpdate(BaseModel):
name: str | None = None
domain: str | None = None
logo_url: str | None = None
category: str | None = None
is_active: bool | None = None
priority: int | None = None
requires_js: bool | None = None
class SearchSiteResponse(BaseModel):
id: int
name: str
domain: str
logo_url: str | None = None
category: str | None = None
is_active: bool
priority: int
requires_js: bool
created_at: datetime | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
# === Search Results Schemas ===
class SearchQuery(BaseModel):
query: str
site_ids: list[int] | None = None # If None, use all active sites
max_results: int = 20
class SearchResultItem(BaseModel):
"""Un produit trouvé lors de la recherche"""
url: str
title: str
price: float | None = None
currency: str = "EUR"
in_stock: bool | None = None
image_url: str | None = None
site_name: str
site_domain: str
confidence: float | None = None
error: str | None = None
class SearchProgress(BaseModel):
"""Progression de la recherche pour SSE"""
status: str # "searching", "scraping", "completed", "error"
total: int
completed: int
current_site: str | None = None
results: list[SearchResultItem] = []
message: str | None = None