Add monitoring TG service

This commit is contained in:
Grendgi
2026-06-04 14:55:41 +03:00
commit f9e072774c
74 changed files with 7232 additions and 0 deletions

119
src/parser_bot/db/models.py Normal file
View File

@@ -0,0 +1,119 @@
from datetime import datetime
from sqlalchemy import (
BigInteger,
DateTime,
ForeignKey,
Index,
String,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class Section(Base):
"""A sub-section inside a vertical, e.g. ('real_estate', 'dubai').
The pair (vertical, slug) is unique and identifies a section in URLs
and API calls. A channel belongs to exactly one section, the section
knows its vertical, and the LLM prompt store can hold a per-section
override that falls back to the vertical-level prompt.
"""
__tablename__ = "sections"
__table_args__ = (
UniqueConstraint("vertical", "slug", name="uq_section_vertical_slug"),
Index("ix_sections_vertical", "vertical"),
)
id: Mapped[int] = mapped_column(primary_key=True)
vertical: Mapped[str] = mapped_column(String(32))
slug: Mapped[str] = mapped_column(String(64))
title: Mapped[str] = mapped_column(String(255))
emoji: Mapped[str | None] = mapped_column(String(8), nullable=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
access_code: Mapped[str | None] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
channels: Mapped[list["Channel"]] = relationship(back_populates="section")
class Channel(Base):
__tablename__ = "channels"
id: Mapped[int] = mapped_column(primary_key=True)
# Telegram numeric channel id (peer id), nullable until first resolve
tg_id: Mapped[int | None] = mapped_column(BigInteger, unique=True, nullable=True)
# Username or t.me/joinchat link supplied by user
identifier: Mapped[str] = mapped_column(String(255), unique=True)
title: Mapped[str | None] = mapped_column(String(512), nullable=True)
# 'real_estate' or 'hr' — picks which LLM prompt and lead schema is used
vertical: Mapped[str] = mapped_column(
String(32), default="real_estate", server_default="real_estate", index=True
)
section_id: Mapped[int] = mapped_column(
ForeignKey("sections.id", ondelete="RESTRICT"), index=True
)
is_active: Mapped[bool] = mapped_column(default=True, server_default="true")
last_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
last_polled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
section: Mapped[Section] = relationship(back_populates="channels")
messages: Mapped[list["Message"]] = relationship(
back_populates="channel",
cascade="all, delete-orphan",
passive_deletes=True,
)
class Message(Base):
__tablename__ = "messages"
__table_args__ = (
UniqueConstraint("channel_id", "tg_message_id", name="uq_channel_message"),
Index("ix_messages_channel_date", "channel_id", "date"),
)
id: Mapped[int] = mapped_column(primary_key=True)
channel_id: Mapped[int] = mapped_column(ForeignKey("channels.id", ondelete="CASCADE"))
tg_message_id: Mapped[int] = mapped_column(BigInteger)
date: Mapped[datetime] = mapped_column(DateTime(timezone=True))
text: Mapped[str | None] = mapped_column(Text, nullable=True)
sender_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
sender_username: Mapped[str | None] = mapped_column(String(64), nullable=True)
sender_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
grouped_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
has_media: Mapped[bool] = mapped_column(default=False, server_default="false")
views: Mapped[int | None] = mapped_column(nullable=True)
forwards: Mapped[int | None] = mapped_column(nullable=True)
raw: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
media_files: Mapped[list | None] = mapped_column(JSONB, nullable=True)
extracted: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
fetched_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
channel: Mapped[Channel] = relationship(back_populates="messages")
class AppSetting(Base):
"""Runtime-editable settings, edited from the UI without a restart."""
__tablename__ = "app_settings"
key: Mapped[str] = mapped_column(String(128), primary_key=True)
value: Mapped[dict | str | int | bool | None] = mapped_column(JSONB, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)