from datetime import datetime, timezone from sqlalchemy import ( JSON, Boolean, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from .database import Base class MapObject(Base): __tablename__ = "map_objects" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, default="") type: Mapped[str] = mapped_column(String(50), nullable=False) latitude: Mapped[float] = mapped_column(Float, nullable=False) longitude: Mapped[float] = mapped_column(Float, nullable=False) event_id: Mapped[int | None] = mapped_column( ForeignKey("events.id", ondelete="SET NULL"), nullable=True, index=True, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), ) media: Mapped[list["ObjectMedia"]] = relationship( back_populates="object", cascade="all, delete-orphan", ) event: Mapped["Event | None"] = relationship(back_populates="map_object") class ObjectMedia(Base): __tablename__ = "object_media" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) object_id: Mapped[int] = mapped_column( ForeignKey("map_objects.id", ondelete="CASCADE"), nullable=False, index=True, ) stored_name: Mapped[str] = mapped_column(String(255), nullable=False) original_name: Mapped[str] = mapped_column(String(255), nullable=False) content_type: Mapped[str] = mapped_column(String(100), nullable=False) size: Mapped[int] = mapped_column(Integer, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), ) object: Mapped["MapObject"] = relationship(back_populates="media") class Event(Base): __tablename__ = "events" __table_args__ = (UniqueConstraint("source_url", name="uq_events_source_url"),) id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) source_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True) source_url: Mapped[str] = mapped_column(String(512), nullable=False) raw_text: Mapped[str] = mapped_column(Text, default="") title: Mapped[str] = mapped_column(String(512), default="") description: Mapped[str] = mapped_column(Text, default="") locality: Mapped[str] = mapped_column(String(255), default="") latitude: Mapped[float | None] = mapped_column(Float, nullable=True) longitude: Mapped[float | None] = mapped_column(Float, nullable=True) event_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) ingested_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), index=True, ) region: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True) topic: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True) tags: Mapped[list | None] = mapped_column(JSON, nullable=True) metadata_: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True) map_object: Mapped["MapObject | None"] = relationship( back_populates="event", uselist=False, ) class ParseJob(Base): __tablename__ = "parse_jobs" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) source_type: Mapped[str] = mapped_column(String(50), nullable=False) source_config: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) schedule: Mapped[str | None] = mapped_column(String(100), nullable=True) status: Mapped[str] = mapped_column(String(50), default="pending", index=True) last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), ) class Consumer(Base): __tablename__ = "consumers" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) api_key_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), ) filter: Mapped["ConsumerFilter | None"] = relationship( back_populates="consumer", uselist=False, cascade="all, delete-orphan", ) class ConsumerFilter(Base): __tablename__ = "consumer_filters" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) consumer_id: Mapped[int] = mapped_column( ForeignKey("consumers.id", ondelete="CASCADE"), nullable=False, unique=True, ) regions: Mapped[list | None] = mapped_column(JSON, nullable=True) topics: Mapped[list | None] = mapped_column(JSON, nullable=True) min_access_level: Mapped[int] = mapped_column(Integer, default=0) date_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) consumer: Mapped["Consumer"] = relationship(back_populates="filter")