1167d138a3
- Locale-aware templates: CommandTemplateSlot now has a locale column, allowing each slot to have per-language variants (EN/RU). Templates are resolved at runtime from the Telegram user's language_code. - Merged system configs: "Default Commands (EN)" and "(RU)" merged into a single "Default Commands" config with locale-aware slots. Migration handles existing data automatically. - Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS replaced with desc_* template slots (desc_status, desc_help, etc.) that users can customize per locale. setMyCommands registers all locales explicitly. - Removed locale from CommandConfig: no longer needed since locale is derived from the Telegram user's language at runtime. - Debounced command auto-sync: after command config/tracker changes, affected bots are marked dirty and synced after a 30s debounce window. Manual "Sync with Telegram" button still works. - Entity pickers in LinkedTargetsSection: replaced 6 plain <select> elements with EntitySelect components (search, icons, keyboard nav). Added onselect callback and size="sm" props to EntitySelect.
421 lines
15 KiB
Python
421 lines
15 KiB
Python
"""SQLModel database table definitions for Notify Bridge."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy import UniqueConstraint, Text
|
|
from sqlmodel import JSON, Column, Field, SQLModel
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class User(SQLModel, table=True):
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
username: str = Field(index=True, unique=True)
|
|
hashed_password: str
|
|
role: str = Field(default="user")
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class ServiceProvider(SQLModel, table=True):
|
|
"""A service provider instance (e.g., an Immich server)."""
|
|
|
|
__tablename__ = "service_provider"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
type: str # ServiceProviderType value ("immich")
|
|
name: str
|
|
icon: str = Field(default="")
|
|
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TelegramBot(SQLModel, table=True):
|
|
__tablename__ = "telegram_bot"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
name: str
|
|
token: str
|
|
icon: str = Field(default="")
|
|
bot_username: str = Field(default="")
|
|
bot_id: int = Field(default=0)
|
|
webhook_path_id: str = Field(default_factory=lambda: uuid4().hex)
|
|
update_mode: str = Field(default="polling") # "polling" or "webhook"
|
|
# NOTE: commands_config column remains in the DB for backward compat,
|
|
# but is no longer part of the SQLModel class. Data migrated to CommandConfig.
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class MatrixBot(SQLModel, table=True):
|
|
"""Matrix bot — homeserver connection for sending messages to rooms."""
|
|
|
|
__tablename__ = "matrix_bot"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
name: str
|
|
icon: str = Field(default="")
|
|
homeserver_url: str # e.g. https://matrix.org
|
|
access_token: str
|
|
display_name: str = Field(default="")
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class EmailBot(SQLModel, table=True):
|
|
"""Email sender — SMTP connection for sending email notifications."""
|
|
|
|
__tablename__ = "email_bot"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
name: str
|
|
icon: str = Field(default="")
|
|
email: str # From address
|
|
smtp_host: str
|
|
smtp_port: int = Field(default=587)
|
|
smtp_username: str = Field(default="")
|
|
smtp_password: str = Field(default="")
|
|
smtp_use_tls: bool = Field(default=True)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TelegramChat(SQLModel, table=True):
|
|
__tablename__ = "telegram_chat"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
bot_id: int = Field(foreign_key="telegram_bot.id")
|
|
chat_id: str
|
|
title: str = Field(default="")
|
|
chat_type: str = Field(default="private")
|
|
username: str = Field(default="")
|
|
discovered_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TrackingConfig(SQLModel, table=True):
|
|
"""What events to track + scheduling rules. Tied to a provider type."""
|
|
|
|
__tablename__ = "tracking_config"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
provider_type: str # Must match provider's type
|
|
name: str
|
|
icon: str = Field(default="")
|
|
|
|
# Event-driven tracking
|
|
track_assets_added: bool = Field(default=True)
|
|
track_assets_removed: bool = Field(default=False)
|
|
track_collection_renamed: bool = Field(default=True)
|
|
track_collection_deleted: bool = Field(default=True)
|
|
track_sharing_changed: bool = Field(default=False)
|
|
track_images: bool = Field(default=True)
|
|
track_videos: bool = Field(default=True)
|
|
notify_favorites_only: bool = Field(default=False)
|
|
|
|
# Asset display
|
|
include_tags: bool = Field(default=True)
|
|
include_asset_details: bool = Field(default=False)
|
|
max_assets_to_show: int = Field(default=5)
|
|
assets_order_by: str = Field(default="none")
|
|
assets_order: str = Field(default="descending")
|
|
|
|
# Periodic summary
|
|
periodic_enabled: bool = Field(default=False)
|
|
periodic_interval_days: int = Field(default=1)
|
|
periodic_start_date: str = Field(default="2025-01-01")
|
|
periodic_times: str = Field(default="12:00")
|
|
|
|
# Scheduled assets
|
|
scheduled_enabled: bool = Field(default=False)
|
|
scheduled_times: str = Field(default="09:00")
|
|
scheduled_collection_mode: str = Field(default="per_collection")
|
|
scheduled_limit: int = Field(default=10)
|
|
scheduled_favorite_only: bool = Field(default=False)
|
|
scheduled_asset_type: str = Field(default="all")
|
|
scheduled_min_rating: int = Field(default=0)
|
|
scheduled_order_by: str = Field(default="random")
|
|
scheduled_order: str = Field(default="descending")
|
|
|
|
# Memory mode
|
|
memory_enabled: bool = Field(default=False)
|
|
memory_source: str = Field(default="albums") # "albums" or "native"
|
|
memory_times: str = Field(default="09:00")
|
|
memory_collection_mode: str = Field(default="combined")
|
|
memory_limit: int = Field(default=10)
|
|
memory_favorite_only: bool = Field(default=False)
|
|
memory_asset_type: str = Field(default="all")
|
|
memory_min_rating: int = Field(default=0)
|
|
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TemplateConfig(SQLModel, table=True):
|
|
"""Jinja2 message templates. Tied to a provider type.
|
|
|
|
Template content is stored in TemplateSlot child rows (one per slot).
|
|
"""
|
|
|
|
__tablename__ = "template_config"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(default=0) # 0 = system-owned (no FK to allow sentinel)
|
|
provider_type: str # Must match provider's type
|
|
name: str
|
|
description: str = Field(default="")
|
|
icon: str = Field(default="")
|
|
locale: str = Field(default="") # e.g. "en", "ru"; empty = unspecified
|
|
|
|
date_format: str = Field(default="%d.%m.%Y, %H:%M UTC")
|
|
date_only_format: str = Field(default="%d.%m.%Y")
|
|
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TemplateSlot(SQLModel, table=True):
|
|
"""One Jinja2 template for a specific slot within a TemplateConfig.
|
|
|
|
Slot names are provider-specific (e.g. 'message_assets_added' for Immich).
|
|
"""
|
|
|
|
__tablename__ = "template_slot"
|
|
__table_args__ = (
|
|
UniqueConstraint("config_id", "slot_name", name="uq_template_slot"),
|
|
)
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
config_id: int = Field(foreign_key="template_config.id", index=True)
|
|
slot_name: str
|
|
template: str = Field(default="", sa_column=Column(Text, default=""))
|
|
|
|
|
|
class NotificationTarget(SQLModel, table=True):
|
|
"""Where to send notifications. Pure delivery endpoint.
|
|
|
|
Target-level config holds connection/display settings (e.g. bot_token,
|
|
disable_url_preview). Actual delivery endpoints live in TargetReceiver rows.
|
|
"""
|
|
|
|
__tablename__ = "notification_target"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
type: str # "telegram", "webhook", "email", "discord", "slack", "ntfy", "matrix"
|
|
name: str
|
|
icon: str = Field(default="")
|
|
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
chat_action: str | None = Field(default="typing") # e.g. "typing", "upload_photo"
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class TargetReceiver(SQLModel, table=True):
|
|
"""One delivery endpoint within a NotificationTarget (broadcast support).
|
|
|
|
For Telegram: config = {"chat_id": "12345"}
|
|
For Webhook: config = {"url": "https://...", "headers": {...}}
|
|
For Email: config = {"email": "user@example.com", "name": "..."}
|
|
"""
|
|
|
|
__tablename__ = "target_receiver"
|
|
__table_args__ = (
|
|
UniqueConstraint("target_id", "receiver_key", name="uq_target_receiver"),
|
|
)
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
target_id: int = Field(foreign_key="notification_target.id", index=True)
|
|
name: str = Field(default="")
|
|
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
receiver_key: str = Field(default="") # dedup key (e.g. chat_id, url, email)
|
|
enabled: bool = Field(default=True)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class NotificationTracker(SQLModel, table=True):
|
|
"""Watches a provider's collections for changes."""
|
|
|
|
__tablename__ = "notification_tracker"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
provider_id: int = Field(foreign_key="service_provider.id")
|
|
name: str
|
|
icon: str = Field(default="")
|
|
collection_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
|
|
scan_interval: int = Field(default=60)
|
|
batch_duration: int = Field(default=0) # seconds to accumulate events before dispatch (0=immediate)
|
|
enabled: bool = Field(default=True)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class NotificationTrackerTarget(SQLModel, table=True):
|
|
"""Junction between NotificationTracker and NotificationTarget with per-link config."""
|
|
|
|
__tablename__ = "notification_tracker_target"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
# Python attr stays as tracker_id for backward compat; DB column is notification_tracker_id
|
|
tracker_id: int = Field(
|
|
foreign_key="notification_tracker.id",
|
|
index=True,
|
|
sa_column_kwargs={"name": "notification_tracker_id"},
|
|
)
|
|
target_id: int = Field(foreign_key="notification_target.id", index=True)
|
|
tracking_config_id: int | None = Field(
|
|
default=None, foreign_key="tracking_config.id"
|
|
)
|
|
template_config_id: int | None = Field(
|
|
default=None, foreign_key="template_config.id"
|
|
)
|
|
enabled: bool = Field(default=True)
|
|
quiet_hours_start: str | None = None
|
|
quiet_hours_end: str | None = None
|
|
# NOTE: commands_config column remains in the DB for backward compat,
|
|
# but is no longer part of the SQLModel class. Data migrated to CommandConfig.
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class NotificationTrackerState(SQLModel, table=True):
|
|
"""Persisted state for change detection."""
|
|
|
|
__tablename__ = "notification_tracker_state"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
# Python attr stays as tracker_id for backward compat; DB column is notification_tracker_id
|
|
tracker_id: int = Field(
|
|
foreign_key="notification_tracker.id",
|
|
sa_column_kwargs={"name": "notification_tracker_id"},
|
|
)
|
|
collection_id: str
|
|
collection_name: str = Field(default="")
|
|
shared: bool = Field(default=False)
|
|
asset_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
|
|
pending_asset_ids: list[str] = Field(default_factory=list, sa_column=Column(JSON))
|
|
last_updated: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class CommandConfig(SQLModel, table=True):
|
|
"""Configuration for bot commands (e.g., which commands are enabled, rate limits)."""
|
|
|
|
__tablename__ = "command_config"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
provider_type: str
|
|
name: str
|
|
icon: str = Field(default="")
|
|
enabled_commands: list[str] = Field(default_factory=list, sa_column=Column(JSON))
|
|
response_mode: str = Field(default="media") # "media" or "text"
|
|
default_count: int = Field(default=5)
|
|
rate_limits: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
command_template_config_id: int | None = Field(
|
|
default=None, foreign_key="command_template_config.id"
|
|
)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class CommandTemplateConfig(SQLModel, table=True):
|
|
"""Jinja2 templates for command responses. Provider-specific via slots."""
|
|
|
|
__tablename__ = "command_template_config"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(default=0) # 0 = system-owned
|
|
provider_type: str
|
|
name: str
|
|
description: str = Field(default="")
|
|
icon: str = Field(default="")
|
|
locale: str = Field(default="") # e.g. "en", "ru"; empty = unspecified
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class CommandTemplateSlot(SQLModel, table=True):
|
|
"""One Jinja2 template for a specific command response slot and locale.
|
|
|
|
Slot names match command names (e.g. 'status', 'help', 'albums').
|
|
Description slots use 'desc_' prefix (e.g. 'desc_status', 'desc_help').
|
|
Each (config, slot, locale) triple holds a separate template.
|
|
"""
|
|
|
|
__tablename__ = "command_template_slot"
|
|
__table_args__ = (
|
|
UniqueConstraint("config_id", "slot_name", "locale", name="uq_cmd_slot_locale"),
|
|
)
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
config_id: int = Field(foreign_key="command_template_config.id", index=True)
|
|
slot_name: str
|
|
locale: str = Field(default="en")
|
|
template: str = Field(default="", sa_column=Column(Text, default=""))
|
|
|
|
|
|
class CommandTracker(SQLModel, table=True):
|
|
"""Links a provider to a command config for interactive bot commands."""
|
|
|
|
__tablename__ = "command_tracker"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
user_id: int = Field(foreign_key="user.id")
|
|
provider_id: int = Field(foreign_key="service_provider.id")
|
|
command_config_id: int = Field(foreign_key="command_config.id")
|
|
name: str
|
|
icon: str = Field(default="")
|
|
enabled: bool = Field(default=True)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class CommandTrackerListener(SQLModel, table=True):
|
|
"""Links a CommandTracker to a listener (e.g., a telegram bot chat)."""
|
|
|
|
__tablename__ = "command_tracker_listener"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"command_tracker_id", "listener_type", "listener_id",
|
|
name="uq_command_tracker_listener",
|
|
),
|
|
)
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
command_tracker_id: int = Field(foreign_key="command_tracker.id")
|
|
listener_type: str # e.g. "telegram_bot"
|
|
listener_id: int
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class EventLog(SQLModel, table=True):
|
|
"""Log of detected events."""
|
|
|
|
__tablename__ = "event_log"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
# Python attr stays as tracker_id for backward compat; DB column is notification_tracker_id
|
|
tracker_id: int | None = Field(
|
|
default=None,
|
|
foreign_key="notification_tracker.id",
|
|
index=True,
|
|
sa_column_kwargs={"name": "notification_tracker_id"},
|
|
)
|
|
tracker_name: str = Field(default="")
|
|
provider_id: int | None = Field(default=None, index=True)
|
|
provider_name: str = Field(default="")
|
|
event_type: str = Field(index=True)
|
|
collection_id: str
|
|
collection_name: str
|
|
assets_count: int = Field(default=0)
|
|
details: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class AppSetting(SQLModel, table=True):
|
|
"""Key-value app-level settings (admin-configurable)."""
|
|
|
|
__tablename__ = "app_setting"
|
|
|
|
key: str = Field(primary_key=True)
|
|
value: str = Field(default="")
|