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

209 lines
4.8 KiB
Python

from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
ObjectType = Literal["point", "marker", "zone", "other"]
class MapObjectCreate(BaseModel):
name: str = Field(min_length=1, max_length=255)
description: str = ""
type: ObjectType
latitude: float = Field(ge=-90, le=90)
longitude: float = Field(ge=-180, le=180)
created_at: datetime | None = None
class MapObjectRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
description: str
type: ObjectType
latitude: float
longitude: float
event_id: int | None = None
created_at: datetime
class MapObjectWithEvent(MapObjectRead):
event_date: datetime | None = None
locality: str | None = None
region: str | None = None
topic: str | None = None
source_type: str | None = None
source_url: str | None = None
title: str | None = None
class MapDateBounds(BaseModel):
min: str | None = None
max: str | None = None
class MapFiltersRead(BaseModel):
regions: list[str]
topics: list[str]
source_types: list[str]
available_dates: list[str]
date_bounds: MapDateBounds
class MapObjectUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = None
type: ObjectType | None = None
latitude: float | None = Field(default=None, ge=-90, le=90)
longitude: float | None = Field(default=None, ge=-180, le=180)
created_at: datetime | None = None
class ObjectMediaRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
object_id: int
original_name: str
content_type: str
size: int
created_at: datetime
url: str
class EventRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
source_type: str
source_url: str
raw_text: str
title: str
description: str
locality: str
latitude: float | None
longitude: float | None
event_date: datetime | None
ingested_at: datetime
region: str | None
topic: str | None
tags: list[str] | None
metadata: dict[str, Any] | None = Field(validation_alias="metadata_")
class IngestEventItem(BaseModel):
source_type: str = "telegram"
source_url: str
raw_text: str = ""
title: str = ""
description: str = ""
locality: str = ""
latitude: float | None = None
longitude: float | None = None
event_date: datetime | None = None
region: str | None = None
topic: str | None = None
tags: list[str] | None = None
metadata: dict[str, Any] | None = None
class IngestRequest(BaseModel):
job_id: int | None = None
events: list[IngestEventItem] = Field(default_factory=list)
listener: bool = False
class ListenerSubscription(BaseModel):
job_id: int
channel: str
class IngestResponse(BaseModel):
ingested: int
updated: int
skipped: int = 0
map_objects_synced: int
class ParseJobCreate(BaseModel):
source_type: str = "telegram"
source_config: dict[str, Any] = Field(default_factory=dict)
schedule: str | None = None
interval_seconds: int = Field(default=3600, ge=60, le=604800)
is_active: bool = True
class ParseJobUpdate(BaseModel):
source_config: dict[str, Any] | None = None
interval_seconds: int | None = Field(default=None, ge=60, le=604800)
is_active: bool | None = None
class ParseJobRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
source_type: str
source_config: dict[str, Any]
schedule: str | None
interval_seconds: int
is_active: bool
status: str
last_run_at: datetime | None
last_error: str | None
created_at: datetime
class ConsumerCreate(BaseModel):
name: str
regions: list[str] | None = None
topics: list[str] | None = None
date_from: datetime | None = None
class ConsumerUpdate(BaseModel):
name: str | None = None
is_active: bool | None = None
regions: list[str] | None = None
topics: list[str] | None = None
date_from: datetime | None = None
class ConsumerRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
is_active: bool
created_at: datetime
api_key: str | None = None
regions: list[str] | None = None
topics: list[str] | None = None
date_from: datetime | None = None
class EventListResponse(BaseModel):
items: list[EventRead]
total: int
class AnalyticsSummary(BaseModel):
total_events: int
events_with_coords: int
events_last_24h: int
total_consumers: int
active_consumers: int
total_jobs: int
pending_jobs: int
class TimelinePoint(BaseModel):
date: str
count: int
class TopItem(BaseModel):
name: str
count: int