feat: comprehensive code review fixes — security, performance, quality
Backend security: - Reject Gitea webhooks when webhook_secret is empty (was silently skipping) - Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints - Add CORS middleware with configurable origins - Mask telegram_webhook_secret in settings API response - Protect system-owned command template configs from regular user modification - Increase minimum password length to 8 characters Backend performance: - Batch queries in _resolve_command_context (3 queries instead of 3N) - Concurrent album fetching with asyncio.gather in immich commands - Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation) - TTLCache for rate limits (bounded memory, auto-eviction) - Optional aiohttp session reuse in send_reply/send_media_group Backend code quality: - Extract dispatch_helpers.py (shared link_data loading + event filtering) - Extract database/seeds.py from main.py (490 lines → dedicated module) - Split immich_handler.py (415 lines) into commands/immich/ subpackage - Replace bare except blocks with logged warnings - Add per-provider config validation (Pydantic models) - Truncate command input to 512 chars - Expose usage_* and desc_* slots in capabilities and variables API Frontend security: - CSS.escape() for user-controlled querySelector in highlight.ts - Client-side password min 8 chars validation on setup and password change Frontend code quality: - Replace any types with proper interfaces across top files - Decompose targets/+page.svelte into TargetForm + ReceiverSection - Fix $derived.by usage, $state mutation patterns - Add console.warn to empty catch blocks Frontend UX: - Auth redirect via goto() with "Redirecting..." state - Platform-aware Ctrl/Cmd K keyboard hint - Remove stat-card hover transform Frontend accessibility: - Modal: role=dialog, aria-modal, focus trap, restore focus - EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""Shared helpers, imports, and constants for Immich command handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from ...services import make_immich_provider
|
||||
from ..handler import _render_cmd_template
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_IMMICH_COMMANDS = {
|
||||
"status", "albums", "events", "people",
|
||||
"search", "find", "person", "place",
|
||||
"latest", "random", "favorites", "summary", "memory",
|
||||
}
|
||||
|
||||
|
||||
def _format_assets(
|
||||
assets: list[dict[str, Any]], cmd: str, query: str,
|
||||
locale: str, response_mode: str, client: Any,
|
||||
cmd_templates: dict[str, dict[str, str]],
|
||||
) -> str | list[dict[str, Any]]:
|
||||
"""Format asset results as text or media payload."""
|
||||
if not assets:
|
||||
return _render_cmd_template(cmd_templates, "no_results", locale, {"command": cmd, "query": query})
|
||||
|
||||
if response_mode == "media":
|
||||
media_items = []
|
||||
for asset in assets:
|
||||
asset_id = asset.get("id", "")
|
||||
filename = asset.get("originalFileName", "")
|
||||
year = asset.get("year", "")
|
||||
caption = f"{filename} ({year})" if year else filename
|
||||
media_items.append({
|
||||
"type": "photo",
|
||||
"asset_id": asset_id,
|
||||
"caption": caption,
|
||||
"thumbnail_url": f"{client.url}/api/assets/{asset_id}/thumbnail?size=preview",
|
||||
"api_key": client.api_key,
|
||||
})
|
||||
return media_items
|
||||
|
||||
slot_map = {"find": "search", "person": "search", "place": "search"}
|
||||
slot_name = slot_map.get(cmd, cmd)
|
||||
return _render_cmd_template(cmd_templates, slot_name, locale, {
|
||||
"assets": assets, "query": query, "command": cmd, "count": len(assets),
|
||||
})
|
||||
Reference in New Issue
Block a user