Files
gitrusprusandCursor 1492576fd9 Add Telegram listener, scheduler, and extended parsers admin UI.
Introduce background scheduling and migrations for analytics ingest, expand parser management and map toolbar UX, and ignore local data/session files from version control.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 20:47:56 +03:00

148 lines
5.7 KiB
Python

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)
interval_seconds: Mapped[int] = mapped_column(Integer, default=3600, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
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")