Some checks failed
Validate / Hassfest (push) Has been cancelled
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>
158 lines
5.1 KiB
Python
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
|