Files
haos-hacs-immich-album-watcher/packages/server/src/immich_watcher_server/api/tracking_configs.py
alexei.dolgolyov 90b4713d5c
Some checks failed
Validate / Hassfest (push) Has been cancelled
Restructure data model: TrackingConfig + TemplateConfig entities
Major model restructuring for clean separation of concerns:

New entities:
- TrackingConfig: What to react to (event types, asset filters,
  periodic/scheduled/memory mode config) - reusable across targets
- TemplateConfig: All ~15 template slots from blueprint (event
  messages, asset formatting, date/location, scheduled messages)
  with full defaults - separate entities per locale

Changed entities:
- AlbumTracker: Simplified to album selection + polling + target_ids
  (removed event_types, template_id, all filter fields)
- NotificationTarget: Extended with tracking_config_id and
  template_config_id FKs (many-to-one, reusable configs)

Removed entities:
- MessageTemplate (replaced by TemplateConfig)
- ScheduledJob (absorbed into TrackingConfig)

Updated services:
- watcher.py: Each target checked against its own tracking_config
  for event filtering before sending notification
- notifier.py: Uses target's template_config to select the right
  template slot based on event type

New API routes:
- /api/tracking-configs/* (CRUD)
- /api/template-configs/* (CRUD + per-slot preview)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:57:19 +03:00

158 lines
5.1 KiB
Python

"""Tracking configuration CRUD API routes."""
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from ..auth.dependencies import get_current_user
from ..database.engine import get_session
from ..database.models import TrackingConfig, User
router = APIRouter(prefix="/api/tracking-configs", tags=["tracking-configs"])
class TrackingConfigCreate(BaseModel):
name: str
track_assets_added: bool = True
track_assets_removed: bool = False
track_album_renamed: bool = True
track_album_deleted: bool = True
track_images: bool = True
track_videos: bool = True
notify_favorites_only: bool = False
include_people: bool = True
include_asset_details: bool = False
max_assets_to_show: int = 5
assets_order_by: str = "none"
assets_order: str = "descending"
periodic_enabled: bool = False
periodic_interval_days: int = 1
periodic_start_date: str = "2025-01-01"
periodic_times: str = "12:00"
scheduled_enabled: bool = False
scheduled_times: str = "09:00"
scheduled_album_mode: str = "per_album"
scheduled_limit: int = 10
scheduled_favorite_only: bool = False
scheduled_asset_type: str = "all"
scheduled_min_rating: int = 0
scheduled_order_by: str = "random"
scheduled_order: str = "descending"
memory_enabled: bool = False
memory_times: str = "09:00"
memory_album_mode: str = "combined"
memory_limit: int = 10
memory_favorite_only: bool = False
memory_asset_type: str = "all"
memory_min_rating: int = 0
class TrackingConfigUpdate(BaseModel):
name: str | None = None
track_assets_added: bool | None = None
track_assets_removed: bool | None = None
track_album_renamed: bool | None = None
track_album_deleted: bool | None = None
track_images: bool | None = None
track_videos: bool | None = None
notify_favorites_only: bool | None = None
include_people: bool | None = None
include_asset_details: bool | None = None
max_assets_to_show: int | None = None
assets_order_by: str | None = None
assets_order: str | None = None
periodic_enabled: bool | None = None
periodic_interval_days: int | None = None
periodic_start_date: str | None = None
periodic_times: str | None = None
scheduled_enabled: bool | None = None
scheduled_times: str | None = None
scheduled_album_mode: str | None = None
scheduled_limit: int | None = None
scheduled_favorite_only: bool | None = None
scheduled_asset_type: str | None = None
scheduled_min_rating: int | None = None
scheduled_order_by: str | None = None
scheduled_order: str | None = None
memory_enabled: bool | None = None
memory_times: str | None = None
memory_album_mode: str | None = None
memory_limit: int | None = None
memory_favorite_only: bool | None = None
memory_asset_type: str | None = None
memory_min_rating: int | None = None
@router.get("")
async def list_configs(
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
result = await session.exec(
select(TrackingConfig).where(TrackingConfig.user_id == user.id)
)
return [_response(c) for c in result.all()]
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_config(
body: TrackingConfigCreate,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
config = TrackingConfig(user_id=user.id, **body.model_dump())
session.add(config)
await session.commit()
await session.refresh(config)
return _response(config)
@router.get("/{config_id}")
async def get_config(
config_id: int,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
return _response(await _get(session, config_id, user.id))
@router.put("/{config_id}")
async def update_config(
config_id: int,
body: TrackingConfigUpdate,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
config = await _get(session, config_id, user.id)
for field, value in body.model_dump(exclude_unset=True).items():
setattr(config, field, value)
session.add(config)
await session.commit()
await session.refresh(config)
return _response(config)
@router.delete("/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_config(
config_id: int,
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
):
config = await _get(session, config_id, user.id)
await session.delete(config)
await session.commit()
def _response(c: TrackingConfig) -> dict:
return {k: getattr(c, k) for k in TrackingConfig.model_fields if k != "user_id"} | {
"created_at": c.created_at.isoformat()
}
async def _get(session: AsyncSession, config_id: int, user_id: int) -> TrackingConfig:
config = await session.get(TrackingConfig, config_id)
if not config or config.user_id != user_id:
raise HTTPException(status_code=404, detail="Tracking config not found")
return config