6c3dd67c1b
Add quiet_hours_enabled/start/end to TrackingConfig (HH:MM strings interpreted in the app-level timezone AppSetting). The dispatch path loads the app timezone once per run and passes it through event_allowed_by_config -> in_quiet_hours, so overnight windows like 22:00-07:00 work correctly in any IANA tz. Frontend exposes a Timezone field under Settings and a Quiet Hours section on the Immich tracking-config form with time-picker inputs.
597 lines
22 KiB
Python
597 lines
22 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")
|
|
token_version: int = Field(default=1)
|
|
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))
|
|
webhook_token: str = Field(default_factory=lambda: uuid4().hex)
|
|
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="")
|
|
language_code: str = Field(default="") # auto-detected from Telegram
|
|
language_override: str = Field(default="") # manual override set by user
|
|
commands_enabled: bool = Field(default=False)
|
|
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)
|
|
|
|
# Gitea event tracking
|
|
track_push: bool = Field(default=True)
|
|
track_issue_opened: bool = Field(default=True)
|
|
track_issue_closed: bool = Field(default=True)
|
|
track_issue_commented: bool = Field(default=False)
|
|
track_pr_opened: bool = Field(default=True)
|
|
track_pr_closed: bool = Field(default=True)
|
|
track_pr_merged: bool = Field(default=True)
|
|
track_pr_commented: bool = Field(default=False)
|
|
track_release_published: bool = Field(default=True)
|
|
|
|
# Planka event tracking
|
|
track_card_created: bool = Field(default=True)
|
|
track_card_updated: bool = Field(default=False)
|
|
track_card_moved: bool = Field(default=True)
|
|
track_card_deleted: bool = Field(default=False)
|
|
track_card_commented: bool = Field(default=True)
|
|
track_comment_updated: bool = Field(default=False)
|
|
track_board_created: bool = Field(default=True)
|
|
track_board_updated: bool = Field(default=False)
|
|
track_board_deleted: bool = Field(default=True)
|
|
track_list_created: bool = Field(default=False)
|
|
track_list_updated: bool = Field(default=False)
|
|
track_list_deleted: bool = Field(default=False)
|
|
track_attachment_created: bool = Field(default=True)
|
|
track_card_label_added: bool = Field(default=False)
|
|
track_task_completed: bool = Field(default=True)
|
|
|
|
# Scheduler event tracking
|
|
track_scheduled_message: bool = Field(default=True)
|
|
|
|
# NUT (UPS) event tracking
|
|
track_ups_online: bool = Field(default=True)
|
|
track_ups_on_battery: bool = Field(default=True)
|
|
track_ups_low_battery: bool = Field(default=True)
|
|
track_ups_battery_restored: bool = Field(default=True)
|
|
track_ups_comms_lost: bool = Field(default=True)
|
|
track_ups_comms_restored: bool = Field(default=True)
|
|
track_ups_replace_battery: bool = Field(default=True)
|
|
track_ups_overload: bool = Field(default=True)
|
|
|
|
# Generic Webhook event tracking
|
|
track_webhook_received: bool = Field(default=True)
|
|
|
|
# Immich asset display
|
|
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)
|
|
|
|
# Quiet hours — HH:MM strings interpreted in the app-level timezone
|
|
# (AppSetting "timezone"). Gated by quiet_hours_enabled so an empty window
|
|
# still represents "explicitly disabled" vs "not yet configured".
|
|
quiet_hours_enabled: bool = Field(default=False)
|
|
quiet_hours_start: str | None = Field(default=None)
|
|
quiet_hours_end: str | None = Field(default=None)
|
|
|
|
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 and locale within a TemplateConfig.
|
|
|
|
Slot names are provider-specific (e.g. 'message_assets_added' for Immich).
|
|
Each (config, slot, locale) triple holds a separate template.
|
|
"""
|
|
|
|
__tablename__ = "template_slot"
|
|
__table_args__ = (
|
|
UniqueConstraint("config_id", "slot_name", "locale", name="uq_template_slot_locale"),
|
|
)
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
config_id: int = Field(
|
|
foreign_key="template_config.id",
|
|
index=True,
|
|
|
|
|
|
)
|
|
slot_name: str
|
|
locale: str = Field(default="en")
|
|
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)
|
|
locale: str = Field(default="") # e.g. "en", "ru"; empty = use target default
|
|
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))
|
|
filters: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
scan_interval: int = Field(default=60)
|
|
batch_duration: int = Field(default=0) # seconds to accumulate events before dispatch (0=immediate)
|
|
default_tracking_config_id: int | None = Field(default=None, foreign_key="tracking_config.id")
|
|
default_template_config_id: int | None = Field(default=None, foreign_key="template_config.id")
|
|
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",
|
|
index=True,
|
|
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))
|
|
locale: str = Field(default="en")
|
|
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",
|
|
index=True,
|
|
|
|
)
|
|
listener_type: str # e.g. "telegram_bot"
|
|
listener_id: int
|
|
# Optional per-chat album scope. None = inherit from tracker (use all).
|
|
# When set, only these album/collection ids are queryable from this chat.
|
|
allowed_album_ids: list[str] | None = Field(
|
|
default=None, sa_column=Column(JSON, nullable=True),
|
|
)
|
|
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)
|
|
# Owner. Indexed for the dashboard events query. Nullable only because
|
|
# historical rows (pre-user_id column) may have no owner; new rows always
|
|
# set this directly.
|
|
user_id: int | None = Field(default=None, foreign_key="user.id", index=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="")
|
|
# Links an event back to an Action when the event was emitted by the
|
|
# action runner (``event_type`` starts with ``action_``). Null for
|
|
# notification-tracker events.
|
|
action_id: int | None = Field(
|
|
default=None, foreign_key="action.id", index=True,
|
|
)
|
|
action_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 Action(SQLModel, table=True):
|
|
"""A scheduled action that mutates an external service."""
|
|
|
|
__tablename__ = "action"
|
|
|
|
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="")
|
|
action_type: str # e.g. "auto_organize"
|
|
config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
schedule_type: str = Field(default="interval") # "interval" or "cron"
|
|
schedule_interval: int = Field(default=3600) # seconds
|
|
schedule_cron: str = Field(default="")
|
|
enabled: bool = Field(default=False) # default disabled for safety
|
|
last_run_at: datetime | None = Field(default=None)
|
|
last_run_status: str = Field(default="") # "success", "partial", "failed", ""
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class ActionRule(SQLModel, table=True):
|
|
"""One rule within an Action. Executed in order."""
|
|
|
|
__tablename__ = "action_rule"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
action_id: int = Field(foreign_key="action.id", index=True)
|
|
name: str = Field(default="")
|
|
rule_config: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
enabled: bool = Field(default=True)
|
|
order: int = Field(default=0)
|
|
created_at: datetime = Field(default_factory=_utcnow)
|
|
|
|
|
|
class ActionExecution(SQLModel, table=True):
|
|
"""Log of an action execution (scheduled, manual, or dry-run)."""
|
|
|
|
__tablename__ = "action_execution"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
action_id: int = Field(foreign_key="action.id", index=True)
|
|
started_at: datetime = Field(default_factory=_utcnow)
|
|
finished_at: datetime | None = Field(default=None)
|
|
status: str = Field(default="running") # "running", "success", "partial", "failed"
|
|
rules_processed: int = Field(default=0)
|
|
rules_succeeded: int = Field(default=0)
|
|
rules_failed: int = Field(default=0)
|
|
total_items_affected: int = Field(default=0)
|
|
summary: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
error: str = Field(default="")
|
|
trigger: str = Field(default="scheduled") # "scheduled", "manual", "dry_run"
|
|
|
|
|
|
class WebhookPayloadLog(SQLModel, table=True):
|
|
"""Log of incoming webhook payloads for debugging and replay."""
|
|
|
|
__tablename__ = "webhook_payload_log"
|
|
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
provider_id: int = Field(foreign_key="service_provider.id", index=True)
|
|
method: str = Field(default="POST")
|
|
headers: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
body: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
status: str = Field(default="matched") # "matched" | "unmatched" | "error"
|
|
extracted_fields: dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
|
|
error_message: str = Field(default="")
|
|
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="")
|