This commit is contained in:
Gilles Soulier
2026-01-14 21:54:55 +01:00
parent c91c0f1fc9
commit d0b73b9319
140 changed files with 5822 additions and 161 deletions

48
pricewatch/app/db/models.py Executable file → Normal file
View File

@@ -15,7 +15,7 @@ Justification technique:
- JSONB uniquement pour données variables: errors, notes dans logs
"""
from datetime import datetime
from datetime import datetime, timezone
from decimal import Decimal
from typing import List, Optional
@@ -28,6 +28,7 @@ from sqlalchemy import (
Integer,
JSON,
Numeric,
Boolean,
String,
Text,
UniqueConstraint,
@@ -42,6 +43,10 @@ class Base(DeclarativeBase):
pass
def utcnow() -> datetime:
return datetime.now(timezone.utc)
class Product(Base):
"""
Catalogue produits (1 ligne par produit unique).
@@ -70,19 +75,25 @@ class Product(Base):
category: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="Product category (breadcrumb)"
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="Product description"
)
currency: Mapped[Optional[str]] = mapped_column(
String(3), nullable=True, comment="Currency code (EUR, USD, GBP)"
)
msrp: Mapped[Optional[Decimal]] = mapped_column(
Numeric(10, 2), nullable=True, comment="Recommended price"
)
# Timestamps
first_seen_at: Mapped[datetime] = mapped_column(
TIMESTAMP, nullable=False, default=datetime.utcnow, comment="First scraping timestamp"
TIMESTAMP, nullable=False, default=utcnow, comment="First scraping timestamp"
)
last_updated_at: Mapped[datetime] = mapped_column(
TIMESTAMP,
nullable=False,
default=datetime.utcnow,
onupdate=datetime.utcnow,
default=utcnow,
onupdate=utcnow,
comment="Last metadata update",
)
@@ -280,7 +291,7 @@ class ScrapingLog(Base):
String(20), nullable=False, comment="Fetch status (success, partial, failed)"
)
fetched_at: Mapped[datetime] = mapped_column(
TIMESTAMP, nullable=False, default=datetime.utcnow, comment="Scraping timestamp"
TIMESTAMP, nullable=False, default=utcnow, comment="Scraping timestamp"
)
# Performance metrics
@@ -318,3 +329,30 @@ class ScrapingLog(Base):
def __repr__(self) -> str:
return f"<ScrapingLog(id={self.id}, url={self.url}, status={self.fetch_status}, fetched_at={self.fetched_at})>"
class Webhook(Base):
"""
Webhooks pour notifications externes.
"""
__tablename__ = "webhooks"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
event: Mapped[str] = mapped_column(String(50), nullable=False, comment="Event name")
url: Mapped[str] = mapped_column(Text, nullable=False, comment="Webhook URL")
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
secret: Mapped[Optional[str]] = mapped_column(
String(200), nullable=True, comment="Secret optionnel"
)
created_at: Mapped[datetime] = mapped_column(
TIMESTAMP, nullable=False, default=utcnow, comment="Creation timestamp"
)
__table_args__ = (
Index("ix_webhook_event", "event"),
Index("ix_webhook_enabled", "enabled"),
)
def __repr__(self) -> str:
return f"<Webhook(id={self.id}, event={self.event}, url={self.url})>"