chore: pre-release cleanup

- Skip token clear/redirect on 401 for unauthenticated requests
- Fix typo in test secret key in restart-backend script
- Remove completed plan documents (entity-relationship-refactor, ux-notification-improvements)
This commit is contained in:
2026-04-21 19:39:33 +03:00
parent eecc9e295c
commit 90bc3ccdc2
19 changed files with 2 additions and 959 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ export async function api<T = any>(
}
}
if (res.status === 401) {
if (res.status === 401 && token) {
clearTokens();
if (typeof window !== 'undefined') {
window.location.href = '/login';
@@ -1,53 +0,0 @@
# Feature Context: Entity Relationship Refactor
## Current State
Starting — no changes made yet. Branch created from master with all telegram-commands work merged.
## Key Design Decisions
- Provider capabilities (notifications, commands) inferred from provider type config, not explicit DB flags
- Tracker renamed to NotificationTracker; TrackerTarget renamed to NotificationTrackerTarget
- New entities: CommandConfig, CommandTracker, CommandTrackerListener
- CommandConfig is provider_type-scoped, shareable across multiple CommandTrackers
- CommandTrackerListener is a junction table (command_tracker_id, listener_type, listener_id) for extensibility
- TelegramBot is dual-purpose: notification target backend + commands listener
- TelegramBot polling/webhook lifecycle tied to CommandTrackerListener ref-counting
- Telegram targets gain chat_action field
- commands_config moves from TelegramBot to CommandConfig entity
## Entity Schema (Target State)
```
ServiceProvider (type: "immich" → infers has_notifications=true, has_commands=true)
├─ NotificationTracker (renamed from Tracker)
│ └─ NotificationTrackerTarget (renamed from TrackerTarget)
│ ├─ NotificationTarget (+ chat_action for telegram type)
│ ├─ TrackingConfig (unchanged)
│ └─ TemplateConfig (unchanged)
└─ CommandTracker (new)
├─ CommandConfig (new, shared, provider_type-scoped)
└─ CommandTrackerListener (junction → listener_type + listener_id)
└─ TelegramBot as "telegram_bot" listener type
TelegramBot
├─ Used by NotificationTarget (sending messages)
└─ Used by CommandTrackerListener (receiving commands)
└─ Smart ref-counting: start polling/webhook when first listener added, stop when last removed
```
## Temporary Workarounds
None yet.
## Cross-Phase Dependencies
- Phase 2 depends on Phase 1 (renamed models)
- Phase 3 depends on Phase 1 (new models for CommandConfig, CommandTracker, CommandTrackerListener)
- Phase 4 depends on Phase 3 (command entities exist in DB/API)
- Phase 5 depends on Phase 2 (renamed API endpoints)
- Phase 6 depends on Phase 3 (command entity APIs)
- Phase 7 depends on all prior phases
## Implementation Notes
- SQLite + async SQLAlchemy via sqlmodel — table renames done via idempotent ALTER TABLE / CREATE TABLE
- No formal test suite — verification via server startup + health check + frontend build
- Migration must handle existing data: rename tables, migrate TelegramBot.commands_config → CommandConfig rows
- Incremental strategy: each phase leaves the codebase fully working
@@ -1,52 +0,0 @@
# Feature: Entity Relationship Refactor
**Branch:** `feature/entity-relationship-refactor`
**Base branch:** `master`
**Created:** 2026-03-20
**Status:** ✅ Complete
**Strategy:** Incremental
**Mode:** Automated
**Execution:** Orchestrator
## Summary
Rework the entity schema so that ServiceProvider capabilities (notifications, commands) are
inferred from provider type config. Current Trackers become NotificationTrackers. A new
CommandTracker entity links providers to CommandConfigs and CommandsListeners (TelegramBot
as first implementation). TelegramBot becomes dual-purpose: notification target backend +
commands listener with smart ref-counted polling/webhook. CommandConfig is a new shareable
entity scoped to provider type. Telegram targets gain a chat_action setting.
## Build & Test Commands
- **Build (backend):** `cd packages/server && pip install -e .`
- **Verify (backend):** Server startup + `curl -s http://localhost:8420/api/health`
- **Build (frontend):** `cd frontend && npm install && npx vite build`
- **Test:** No automated test suite yet — verification via server startup and frontend build
## Phases
- [x] Phase 1: Database Schema & Migration [domain: backend] → [subplan](./phase-1-db-schema.md)
- [x] Phase 2: Notification Tracker Rename (API) [domain: backend] → [subplan](./phase-2-notification-tracker-rename.md)
- [x] Phase 3: CommandConfig & CommandTracker CRUD [domain: backend] → [subplan](./phase-3-command-entities-api.md)
- [x] Phase 4: Command System Refactor [domain: backend] → [subplan](./phase-4-command-system-refactor.md)
- [x] Phase 5: Frontend Rename & Restructure [domain: frontend] → [subplan](./phase-5-frontend-rename.md)
- [x] Phase 6: Frontend Command Entities [domain: frontend] → [subplan](./phase-6-frontend-commands.md)
- [x] Phase 7: Integration & Cleanup [domain: fullstack] → [subplan](./phase-7-integration-cleanup.md)
## Phase Progress Log
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 1: DB Schema & Migration | backend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 2: Notification Tracker Rename | backend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 3: Command Entities API | backend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 4: Command System Refactor | backend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 5: Frontend Rename | frontend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 6: Frontend Commands | frontend | ✅ Complete | ✅ | ✅ | ✅ |
| Phase 7: Integration & Cleanup | fullstack | ✅ Complete | ✅ | ✅ | ✅ |
## Final Review
- [x] Comprehensive code review
- [x] Full build passes
- [x] Full test suite passes
- [ ] Merged to `master`
@@ -1,61 +0,0 @@
# Phase 1: Database Schema & Migration
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Add new database models (CommandConfig, CommandTracker, CommandTrackerListener), rename
existing models (Tracker → NotificationTracker, TrackerTarget → NotificationTrackerTarget),
add chat_action to NotificationTarget, and write idempotent migration logic.
## Tasks
- [ ] Task 1: Rename `Tracker` model to `NotificationTracker` — update class name, `__tablename__` to `"notification_tracker"`, and all field references. Keep all existing fields (provider_id, collection_ids, scan_interval, batch_duration, enabled, etc.)
- [ ] Task 2: Rename `TrackerTarget` model to `NotificationTrackerTarget` — update class name, `__tablename__` to `"notification_tracker_target"`, rename `tracker_id` FK to `notification_tracker_id`
- [ ] Task 3: Rename `TrackerState` model to `NotificationTrackerState` — update class name, `__tablename__` to `"notification_tracker_state"`, rename `tracker_id` FK to `notification_tracker_id`
- [ ] Task 4: Add `chat_action` optional string field to `NotificationTarget` model (for telegram targets, e.g. "typing", "upload_photo")
- [ ] Task 5: Create `CommandConfig` model — fields: id, user_id (FK→User), provider_type (str), name, icon, enabled_commands (JSON list), locale (str, default "en"), response_mode (str, default "media"), default_count (int, default 5), rate_limits (JSON dict), created_at
- [ ] Task 6: Create `CommandTracker` model — fields: id, user_id (FK→User), provider_id (FK→ServiceProvider), command_config_id (FK→CommandConfig), name, icon, enabled (bool), created_at
- [ ] Task 7: Create `CommandTrackerListener` model — fields: id, command_tracker_id (FK→CommandTracker), listener_type (str, e.g. "telegram_bot"), listener_id (int), created_at. Add unique constraint on (command_tracker_id, listener_type, listener_id)
- [ ] Task 8: Remove `commands_config` field from `TelegramBot` model (will be migrated to CommandConfig)
- [ ] Task 9: Remove `commands_config` field from `TrackerTarget`/`NotificationTrackerTarget` model
- [ ] Task 10: Write idempotent migration in `migrations.py`:
- Rename table `tracker``notification_tracker`
- Rename table `tracker_target``notification_tracker_target` and rename column `tracker_id``notification_tracker_id`
- Rename table `tracker_state``notification_tracker_state` and rename column `tracker_id``notification_tracker_id`
- Add `chat_action` column to `notification_target`
- Create `command_config` table
- Create `command_tracker` table
- Create `command_tracker_listener` table
- Migrate existing `TelegramBot.commands_config` JSON → `CommandConfig` rows (one per bot that has non-default config)
- Drop `commands_config` column from old telegram_bot table
- Drop `commands_config` column from notification_tracker_target table
- [ ] Task 11: Update all model imports in `models.py` `__init__` / re-exports — ensure other modules can still import the models
- [ ] Task 12: Update `EventLog` model — rename `tracker_id` field to `notification_tracker_id` (nullable FK), add migration for column rename
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/database/models.py` — rename models, add new models, remove fields
- `packages/server/src/notify_bridge_server/database/migrations.py` — add migration functions
## Acceptance Criteria
- All new tables are created on startup via migration
- Existing data is preserved and migrated (table renames, column renames, commands_config → CommandConfig)
- Server starts without errors with existing test-data database
- All existing imports still resolve (may need temporary aliases)
## Notes
- SQLite does not support `ALTER TABLE RENAME COLUMN` in older versions. Use the existing pattern of adding new columns + copying data if needed.
- The migration must be idempotent — safe to run multiple times.
- Other modules (API routes, services) will still reference old model names after this phase. That's OK — Phase 2 will update the API layer. For now, add Python-level aliases (e.g., `Tracker = NotificationTracker`) so existing code continues to work.
- TrackerTarget.commands_config was unused in practice — safe to drop without data loss.
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,60 +0,0 @@
# Phase 2: Notification Tracker Rename (API)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Rename all tracker-related API routes, service functions, and internal references to use
"notification_tracker" naming. Add chat_action support to the targets API. Ensure the
watcher, scheduler, and notifier services work with the renamed models.
## Tasks
- [ ] Task 1: Rename `api/trackers.py``api/notification_trackers.py`. Update all route paths from `/api/trackers` to `/api/notification-trackers`. Update function names (e.g., `list_trackers``list_notification_trackers`). Update all model references to use `NotificationTracker`.
- [ ] Task 2: Rename `api/tracker_targets.py``api/notification_tracker_targets.py`. Update route paths from `/api/tracker-targets` to `/api/notification-tracker-targets`. Update model references to `NotificationTrackerTarget`, field references to `notification_tracker_id`.
- [ ] Task 3: Update `api/targets.py` — add `chat_action` to create/update request schemas and response serialization for telegram-type targets.
- [ ] Task 4: Update `services/watcher.py` — replace all `Tracker` references with `NotificationTracker`, `TrackerTarget` with `NotificationTrackerTarget`, `TrackerState` with `NotificationTrackerState`, `tracker_id` with `notification_tracker_id` where applicable.
- [ ] Task 5: Update `services/scheduler.py` — rename tracker job references, function parameters, and log messages to use notification_tracker naming.
- [ ] Task 6: Update `services/notifier.py` — update model references and any tracker-related parameter names.
- [ ] Task 7: Update `main.py` — change router imports and registration to use new module names and route prefixes.
- [ ] Task 8: Update `api/status.py` — rename any tracker count queries to use new model names.
- [ ] Task 9: Update `commands/handler.py` — update any tracker model references used for command context resolution.
- [ ] Task 10: Update `commands/webhook.py` — update any tracker model references.
- [ ] Task 11: Update `services/telegram_poller.py` — update any tracker model references.
- [ ] Task 12: Remove backward-compatibility aliases from models.py (if added in Phase 1) — all consumers now use new names.
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/api/trackers.py` → rename to `notification_trackers.py`
- `packages/server/src/notify_bridge_server/api/tracker_targets.py` → rename to `notification_tracker_targets.py`
- `packages/server/src/notify_bridge_server/api/targets.py` — add chat_action
- `packages/server/src/notify_bridge_server/services/watcher.py` — model name updates
- `packages/server/src/notify_bridge_server/services/scheduler.py` — model name updates
- `packages/server/src/notify_bridge_server/services/notifier.py` — model name updates
- `packages/server/src/notify_bridge_server/main.py` — router registration
- `packages/server/src/notify_bridge_server/api/status.py` — model name updates
- `packages/server/src/notify_bridge_server/commands/handler.py` — model references
- `packages/server/src/notify_bridge_server/commands/webhook.py` — model references
- `packages/server/src/notify_bridge_server/services/telegram_poller.py` — model references
## Acceptance Criteria
- All API routes work under new `/api/notification-trackers` and `/api/notification-tracker-targets` paths
- Old `/api/trackers` routes no longer exist
- Telegram targets accept and return `chat_action` field
- Server starts and health check passes
- Watcher/scheduler/notifier services function correctly with renamed models
## Notes
- This is a breaking API change — frontend will need updating in Phase 5.
- The watcher service is the most complex consumer of tracker models — test carefully.
- The EventLog model references notification_tracker_id (renamed in Phase 1).
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,72 +0,0 @@
# Phase 3: CommandConfig & CommandTracker CRUD API
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Create full CRUD API routes for CommandConfig, CommandTracker, and CommandTrackerListener
management. These endpoints let users create command configurations (scoped to provider type),
create command trackers that link a provider to a command config, and attach/detach listeners
(telegram bots) to command trackers.
## Tasks
- [ ] Task 1: Create `api/command_configs.py` with CRUD routes:
- `GET /api/command-configs` — list all for current user (+ system defaults with user_id=0)
- `POST /api/command-configs` — create new (validate provider_type, enabled_commands against registry)
- `GET /api/command-configs/{id}` — get single
- `PUT /api/command-configs/{id}` — update (validate ownership)
- `DELETE /api/command-configs/{id}` — delete (check not in use by any command tracker)
- Response should include all fields: id, user_id, provider_type, name, icon, enabled_commands, locale, response_mode, default_count, rate_limits, created_at
- [ ] Task 2: Create `api/command_trackers.py` with CRUD routes:
- `GET /api/command-trackers` — list all for current user, include linked listeners count
- `POST /api/command-trackers` — create new (validate provider_id exists, command_config_id exists, provider_type matches between provider and config)
- `GET /api/command-trackers/{id}` — get single with listeners
- `PUT /api/command-trackers/{id}` — update (name, icon, enabled, command_config_id — validate provider_type match)
- `DELETE /api/command-trackers/{id}` — delete (cascade delete listeners)
- `POST /api/command-trackers/{id}/enable` — enable
- `POST /api/command-trackers/{id}/disable` — disable
- [ ] Task 3: Add listener management endpoints to command_trackers.py:
- `GET /api/command-trackers/{id}/listeners` — list listeners for a command tracker
- `POST /api/command-trackers/{id}/listeners` — add listener (body: {listener_type, listener_id}). Validate: listener exists (e.g., TelegramBot with that ID), no duplicate (unique constraint), user owns the listener.
- `DELETE /api/command-trackers/{id}/listeners/{listener_id}` — remove listener
- [ ] Task 4: Add validation helpers:
- Validate `enabled_commands` against `commands/registry.py` known commands for the given provider_type
- Validate `provider_type` match: CommandConfig.provider_type must match ServiceProvider.type of the CommandTracker's provider
- Validate listener ownership: user must own the TelegramBot being attached
- [ ] Task 5: Register new routers in `main.py`
- [ ] Task 6: Update `api/telegram_bots.py` — remove the commands config endpoints (POST `/telegram-bots/{id}/commands`, GET `/telegram-bots/{id}/commands`) since commands config now lives in CommandConfig entity. Keep the sync-commands endpoint but update it to accept a command_config_id parameter or read from command trackers.
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/api/command_configs.py` — new file
- `packages/server/src/notify_bridge_server/api/command_trackers.py` — new file
- `packages/server/src/notify_bridge_server/main.py` — register new routers
- `packages/server/src/notify_bridge_server/api/telegram_bots.py` — remove old commands config endpoints
## Acceptance Criteria
- Full CRUD for CommandConfig with provider_type validation
- Full CRUD for CommandTracker with provider↔config type matching
- Listener add/remove with ownership validation and uniqueness
- Old telegram bot commands config endpoints removed
- Server starts and all new endpoints respond correctly
## Notes
- The command registry currently defines commands globally. In future, commands could be provider-scoped. For now, validate enabled_commands against the flat registry list.
- CommandConfig with user_id=0 could serve as system defaults (like TemplateConfig), but this is optional for Phase 3.
- The sync-commands endpoint on TelegramBot may need to resolve which commands to sync from attached CommandTrackers — this is wired up in Phase 4.
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,87 +0,0 @@
# Phase 4: Command System Refactor
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Refactor the command handling system to resolve commands through CommandTracker → CommandConfig
instead of TelegramBot.commands_config. Implement smart ref-counted polling/webhook management
for TelegramBot when used as a commands listener. Handle multi-tracker routing (one bot serving
multiple command trackers for different providers).
## Tasks
- [ ] Task 1: Refactor `commands/handler.py``handle_command()`:
- Instead of reading `bot.commands_config`, resolve command config through CommandTrackerListeners:
1. Find all CommandTrackerListener rows where listener_type="telegram_bot" AND listener_id=bot.id
2. Load the associated CommandTracker for each (filter enabled=True)
3. Load CommandConfig for each tracker
4. Load ServiceProvider for each tracker
- For each incoming command, check which CommandConfig(s) have it enabled
- If multiple trackers enable the same command (e.g., two Immich providers with /latest), use the first match or let the user disambiguate (future enhancement — for now, use first enabled match)
- Pass the resolved provider config to command execution functions
- [ ] Task 2: Update `_get_bot_context()` in handler.py:
- Currently finds trackers/providers by matching bot_token in notification target configs
- New approach: resolve through CommandTracker → provider_id → ServiceProvider
- Return a list of (command_tracker, command_config, provider) tuples
- [ ] Task 3: Implement smart ref-counted polling/webhook in `services/telegram_poller.py`:
- Track active listener count per bot: when a CommandTrackerListener is added for a bot, increment ref count; when removed, decrement
- `start_bot_if_needed(bot_id)` — start polling/webhook only if not already running
- `stop_bot_if_unused(bot_id)` — stop polling/webhook only if ref count reaches 0
- Export these functions for use by the command_trackers API (when adding/removing listeners)
- [ ] Task 4: Update `commands/webhook.py`:
- Webhook handler already receives messages for a specific bot (by webhook_path_id)
- Update to use the new command resolution flow from Task 1
- Ensure chat auto-discovery still works
- [ ] Task 5: Update `services/scheduler.py`:
- On startup, instead of starting polling for all bots with update_mode="polling", start polling only for bots that have active CommandTrackerListeners
- Use ref-counting logic from Task 3
- [ ] Task 6: Update telegram bot sync-commands endpoint:
- `POST /api/telegram-bots/{id}/sync-commands` should now:
1. Find all CommandTrackerListeners for this bot
2. Collect all enabled commands across all linked CommandConfigs
3. Merge command lists (union of enabled commands)
4. Call setMyCommands with the merged list
5. Use locale from the first CommandConfig (or a bot-level default)
- [ ] Task 7: Update `services/__init__.py` startup logic:
- On startup, enumerate all enabled CommandTrackers with listeners
- For each unique bot referenced, call `start_bot_if_needed(bot_id)`
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/commands/handler.py` — new command resolution flow
- `packages/server/src/notify_bridge_server/commands/webhook.py` — updated handler
- `packages/server/src/notify_bridge_server/services/telegram_poller.py` — ref-counted polling
- `packages/server/src/notify_bridge_server/services/scheduler.py` — startup logic
- `packages/server/src/notify_bridge_server/services/__init__.py` — startup logic
- `packages/server/src/notify_bridge_server/api/telegram_bots.py` — sync-commands update
## Acceptance Criteria
- Commands resolve through CommandTracker → CommandConfig instead of TelegramBot.commands_config
- Bot polling/webhook starts only when at least one CommandTrackerListener references the bot
- Bot polling/webhook stops when last listener is removed
- Multiple command trackers can share the same bot — commands are merged
- Telegram bot sync-commands syncs the merged command set
- Existing command functionality (search, latest, random, etc.) still works end-to-end
## Notes
- Rate limiting can stay in-memory per (bot_id, chat_id, category) — no schema change needed.
- The handler currently uses `_get_bot_context()` to find providers via notification targets. The new flow resolves providers via CommandTracker.provider_id — this is cleaner and decouples commands from notification targets.
- Edge case: a bot with no CommandTrackerListeners should not poll/webhook. If a user deletes all command trackers referencing a bot, polling should stop.
- Edge case: a command tracker can be disabled (enabled=False) — disabled trackers don't count for ref-counting.
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,75 +0,0 @@
# Phase 5: Frontend — Rename & Restructure
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Rename all tracker-related frontend pages, routes, API calls, and i18n keys to use
"notification tracker" naming. Add chat_action dropdown to telegram target form.
Update navigation.
## Tasks
- [ ] Task 1: Rename route directory `frontend/src/routes/trackers/``frontend/src/routes/notification-trackers/`. Update `+page.svelte` to use new API endpoints (`/api/notification-trackers`, `/api/notification-tracker-targets`).
- [ ] Task 2: Update `+layout.svelte` navigation:
- Change "Trackers" nav item to "Notification Trackers" (or shorter "Notif. Trackers") with route `/notification-trackers`
- Keep icon the same
- [ ] Task 3: Update `frontend/src/lib/i18n/en.json`:
- Rename `tracker.*` keys to `notificationTracker.*`
- Rename `trackerTarget.*` keys to `notificationTrackerTarget.*`
- Add nav key: `nav.notificationTrackers`
- Add `targets.chatAction`, `targets.chatActionHelp` keys
- Remove old `tracker.*` keys
- [ ] Task 4: Update `frontend/src/lib/i18n/ru.json` — same key renames as en.json with Russian translations
- [ ] Task 5: Update `frontend/src/routes/targets/+page.svelte`:
- Add `chat_action` dropdown to telegram target form (options: none/typing/upload_photo/upload_video/upload_document/record_video/record_voice)
- Include chat_action in create/update API calls
- Display chat_action in target list if set
- [ ] Task 6: Update `frontend/src/routes/notification-trackers/+page.svelte` (renamed from trackers):
- All API calls point to `/api/notification-trackers` and `/api/notification-tracker-targets`
- All variable names reflect "notificationTracker" naming
- i18n keys updated to new prefixes
- [ ] Task 7: Update `frontend/src/routes/+page.svelte` (dashboard):
- Update any tracker references/stats to use new API endpoints and naming
- [ ] Task 8: Update any other pages that reference trackers:
- `tracking-configs/+page.svelte` — update if it links to trackers
- `template-configs/+page.svelte` — update if it references trackers
## Files to Modify/Create
- `frontend/src/routes/trackers/+page.svelte` → move to `frontend/src/routes/notification-trackers/+page.svelte`
- `frontend/src/routes/+layout.svelte` — nav updates
- `frontend/src/lib/i18n/en.json` — key renames
- `frontend/src/lib/i18n/ru.json` — key renames
- `frontend/src/routes/targets/+page.svelte` — chat_action
- `frontend/src/routes/+page.svelte` — dashboard updates
## Acceptance Criteria
- Navigation shows "Notification Trackers" linking to `/notification-trackers`
- Notification trackers page works with renamed API endpoints
- Telegram targets have chat_action dropdown
- All i18n keys updated in both en and ru
- Frontend builds without errors
- No references to old `/api/trackers` endpoints remain
## Notes
- The old `/trackers` route should be removed entirely (no redirect needed — this is an admin tool).
- chat_action values map to Telegram's sendChatAction API parameter.
- Keep the UI structure the same — this is a rename, not a redesign.
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,84 +0,0 @@
# Phase 6: Frontend — Command Entities
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Create new frontend pages for CommandConfig and CommandTracker management. Update the
Telegram Bots page to remove inline commands config (now managed via CommandConfig entity)
and show listener status instead.
## Tasks
- [ ] Task 1: Create `frontend/src/routes/command-configs/+page.svelte`:
- List view showing all command configs with name, provider_type badge, enabled command count, locale
- Create form: name, icon, provider_type selector, enabled_commands checkboxes (from registry), locale dropdown, response_mode dropdown, default_count slider, rate_limits inputs
- Edit/delete functionality
- Follow existing page patterns (show/hide form toggle, icon picker, confirm modal for delete)
- [ ] Task 2: Create `frontend/src/routes/command-trackers/+page.svelte`:
- List view showing command trackers: name, provider name, command config name, listener count, enabled status
- Create form: name, icon, provider selector, command_config selector (filtered by matching provider_type), enabled toggle
- Edit/delete functionality
- Expandable section per tracker showing:
- Linked listeners with type badge and name
- "Add Listener" dropdown (select from user's telegram bots)
- Remove listener button per listener
- [ ] Task 3: Update `frontend/src/routes/telegram-bots/+page.svelte`:
- Remove the "Commands" expandable section (command enable/disable checkboxes, locale, response_mode, default_count, rate_limits)
- Replace with "Listener Status" section showing:
- List of command trackers using this bot as a listener
- Each showing: tracker name, provider name, command config name, enabled status
- Link to command tracker page
- Keep: Chats section, Webhook section, Settings section (update_mode)
- [ ] Task 4: Update `frontend/src/routes/+layout.svelte` navigation:
- Add "Command Configs" nav item (route `/command-configs`, icon: settings/cog)
- Add "Command Trackers" nav item (route `/command-trackers`, icon: terminal/command)
- Group navigation logically: Providers, Notification Trackers, Tracking, Templates, Targets, Bots | Command Trackers, Command Configs
- [ ] Task 5: Update `frontend/src/lib/i18n/en.json`:
- Add `commandConfig.*` keys (title, form labels, validation messages)
- Add `commandTracker.*` keys (title, form labels, listener management)
- Add `nav.commandConfigs`, `nav.commandTrackers` keys
- Remove `telegramBot.commands*` keys (moved to commandConfig)
- [ ] Task 6: Update `frontend/src/lib/i18n/ru.json` — same additions/removals as en.json with Russian translations
- [ ] Task 7: Update `frontend/src/routes/+page.svelte` (dashboard):
- Add command tracker count/status to dashboard stats
## Files to Modify/Create
- `frontend/src/routes/command-configs/+page.svelte` — new page
- `frontend/src/routes/command-trackers/+page.svelte` — new page
- `frontend/src/routes/telegram-bots/+page.svelte` — remove commands section, add listener status
- `frontend/src/routes/+layout.svelte` — navigation
- `frontend/src/lib/i18n/en.json` — new keys
- `frontend/src/lib/i18n/ru.json` — new keys
- `frontend/src/routes/+page.svelte` — dashboard
## Acceptance Criteria
- CommandConfig page: full CRUD with provider_type filtering and command checkboxes
- CommandTracker page: full CRUD with provider/config selection and listener management
- Telegram Bots page: no more inline commands config, shows listener status instead
- Navigation includes new pages in logical grouping
- Both i18n languages updated
- Frontend builds without errors
## Notes
- Command checkboxes should show all 13 commands from the registry (help, status, albums, events, summary, latest, memory, random, search, find, person, place, favorites, people).
- Provider_type filtering: when user selects a provider in CommandTracker form, only show CommandConfigs with matching provider_type.
- The telegram bot "Sync with Telegram" button should remain — it now syncs commands from all linked command trackers.
- Follow existing UI patterns closely (ConfirmModal, icon picker, collapsible sections, snackbar notifications).
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
@@ -1,73 +0,0 @@
# Phase 7: Integration & Cleanup
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Final integration pass: verify end-to-end flows, clean up deprecated code paths,
update CLAUDE.md entity relationship documentation, and ensure everything works
together.
## Tasks
- [ ] Task 1: Verify notification flow end-to-end:
- ServiceProvider → NotificationTracker → NotificationTrackerTarget → NotificationTarget
- Watcher detects changes → dispatches through renamed entities
- Scheduled/periodic/memory notifications still work
- [ ] Task 2: Verify command flow end-to-end:
- CommandTracker → CommandConfig + CommandTrackerListener (TelegramBot)
- Incoming command via webhook/polling → resolved through command tracker
- Bot ref-counting: start/stop polling based on listener count
- [ ] Task 3: Clean up deprecated code:
- Remove any remaining backward-compatibility aliases in models.py
- Remove any old route files that were renamed (trackers.py, tracker_targets.py)
- Remove any unused imports
- Ensure no references to old model names remain anywhere
- [ ] Task 4: Update CLAUDE.md "Entity Relationships" section:
- Document new schema: ServiceProvider capabilities, NotificationTracker, CommandTracker, CommandConfig, CommandTrackerListener
- Update the entity relationship diagram
- Update Template System Sync Rules if affected
- [ ] Task 5: Verify migration idempotency:
- Fresh database: all tables created correctly
- Existing database with old schema: migration runs without errors, data preserved
- Running migration twice: no errors
- [ ] Task 6: Clean up any TODO markers left by previous phases
- [ ] Task 7: Verify frontend-backend integration:
- All frontend pages load and display data correctly
- CRUD operations work for all entities
- Command tracker listener add/remove triggers bot polling start/stop
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/database/models.py` — cleanup aliases
- `CLAUDE.md` — update entity relationships documentation
- Various files — cleanup TODOs and unused code
## Acceptance Criteria
- Full notification flow works: provider → notification tracker → target
- Full command flow works: command tracker → command config → listener → bot
- No references to old model/route names remain
- CLAUDE.md accurately documents new entity schema
- Server starts cleanly with both fresh and migrated databases
- Frontend builds and all pages functional
## Notes
- This phase is primarily verification and cleanup — no major new features.
- If integration issues are found, fix them in this phase rather than going back.
- The old plans/entity-relationship-refactor/ files from previous attempts can be kept as historical record.
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- This is the final phase — no handoff needed. -->
@@ -1,19 +0,0 @@
# Feature Context: UX & Notification Improvements
## Current State
Starting implementation. All entity models already have `icon: str` fields. EventLog exists with basic fields. Immich client already has shared link CRUD methods. Frontend uses Svelte 5 + Tailwind v4 with inline form/card pattern on all CRUD pages.
## Temporary Workarounds
- None yet
## Cross-Phase Dependencies
- Phase 3 depends on Phase 2 (enriched event data from API)
- Phase 6 logically follows Phase 5 (link validation informs no-link handling)
- Phases 1, 4 are fully independent
## Implementation Notes
- All overlays MUST use `position: fixed` with inline styles and `z-index: 9999`
- SQLAlchemy async + aiohttp: eager load DB data before aiohttp context
- Jinja2 templates use SandboxedEnvironment
- Icons stored as MDI icon path names (e.g., `mdiCamera`) from `@mdi/js`
- Frontend uses MdiIcon component to render SVG icons
@@ -1,44 +0,0 @@
# Feature: UX & Notification Improvements
**Branch:** `feature/ux-notification-improvements`
**Base branch:** `feature/entity-relationship-refactor`
**Created:** 2026-03-20
**Status:** 🟡 In Progress
**Strategy:** Incremental
**Mode:** Automated
**Execution:** Orchestrator
## Summary
Seven UX and notification improvements: show entity icons, enrich event data, dashboard filtering/sorting, friendly Telegram chat names, bot test messages, album public link validation, and graceful degradation for albums without public links.
## Build & Test Commands
- **Build (backend):** `cd packages/server && pip install -e .`
- **Build (frontend):** `cd frontend && npx vite build`
- **Test (backend):** `cd packages/server && python -m pytest` (if tests exist)
- **Lint:** N/A
## Phases
- [ ] Phase 1: Show Entity Icons on Cards [domain: frontend] → [subplan](./phase-1-entity-icons.md)
- [ ] Phase 2: Enrich Event Data [domain: backend] → [subplan](./phase-2-enrich-events.md)
- [ ] Phase 3: Richer Events Display + Filtering & Sorting [domain: frontend] → [subplan](./phase-3-events-ui.md)
- [ ] Phase 4: Friendly Chat Names + Test Message for Bots [domain: fullstack] → [subplan](./phase-4-chat-names-test-msg.md)
- [ ] Phase 5: Album Public Link Validation [domain: fullstack] → [subplan](./phase-5-link-validation.md)
- [ ] Phase 6: Support Albums Without Public Links [domain: backend] → [subplan](./phase-6-no-link-support.md)
## Phase Progress Log
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 1: Entity Icons | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 2: Enrich Events | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 3: Events UI | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 4: Chat Names + Test | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 5: Link Validation | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 6: No-Link Support | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
## Final Review
- [ ] Comprehensive code review
- [ ] Full build passes
- [ ] Full test suite passes
- [ ] Merged to `feature/entity-relationship-refactor`
@@ -1,44 +0,0 @@
# Phase 1: Show Entity Icons on Cards
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Display the user-selected icon on every entity card (Providers, Trackers, Targets, Telegram Bots). Currently the IconPicker saves the icon but cards use hardcoded default icons.
## Tasks
- [ ] Task 1: Update provider cards in `frontend/src/routes/providers/+page.svelte` to show `provider.icon` (fallback to default server/cloud icon)
- [ ] Task 2: Update tracker cards in `frontend/src/routes/trackers/+page.svelte` to show `tracker.icon` (fallback to default radar icon)
- [ ] Task 3: Update target cards in `frontend/src/routes/targets/+page.svelte` to show `target.icon` (fallback to type-based default)
- [ ] Task 4: Update bot cards in `frontend/src/routes/telegram-bots/+page.svelte` to show `bot.icon` (fallback to robot icon)
- [ ] Task 5: Ensure icon rendering uses MdiIcon component with proper sizing consistent with existing card headers
## Files to Modify/Create
- `frontend/src/routes/providers/+page.svelte` — card header icon
- `frontend/src/routes/trackers/+page.svelte` — card header icon
- `frontend/src/routes/targets/+page.svelte` — card header icon
- `frontend/src/routes/telegram-bots/+page.svelte` — card header icon
## Acceptance Criteria
- Each entity card displays the saved icon if set
- Falls back to a sensible default icon per entity type when no icon is saved
- Icon styling is consistent across all card types
- No regressions in existing card layout or functionality
## Notes
- Icons are stored as MDI path constant names (e.g., the string key from `@mdi/js`)
- The MdiIcon component already exists in `frontend/src/lib/components/MdiIcon.svelte`
- IconPicker component already handles icon selection and stores the value
- Need to check exactly how icon values are stored (full path data vs key name) to render correctly
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,45 +0,0 @@
# Phase 2: Enrich Event Data
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Enrich EventLog with provider name, tracker name, and asset counts so the dashboard can display richer event details. Update the status API to return these fields.
## Tasks
- [ ] Task 1: Add `provider_name`, `tracker_name`, `provider_id` columns to EventLog model (or store in details JSON to avoid migration complexity)
- [ ] Task 2: Update watcher.py event logging to populate provider_name, tracker_name, and assets_count when creating EventLog entries
- [ ] Task 3: Update status.py GET /api/status endpoint to return enriched event fields (provider_name, tracker_name, event_type, collection_name, assets_count, details)
- [ ] Task 4: Add pagination/limit support to the events endpoint (query param `limit`, default 20)
- [ ] Task 5: Add optional filtering query params: `event_type`, `provider_id`, `search` (name match)
- [ ] Task 6: Handle migration for existing EventLog rows (backfill from tracker/provider if possible, or leave empty for old rows)
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/database/models.py` — EventLog model changes
- `packages/server/src/notify_bridge_server/services/watcher.py` — populate new fields on event creation
- `packages/server/src/notify_bridge_server/api/status.py` — enrich response, add filtering/pagination
- `packages/server/src/notify_bridge_server/database/migrations.py` — add columns if using real columns
## Acceptance Criteria
- EventLog entries created after this phase include provider_name, tracker_name, assets_count
- GET /api/status returns enriched event data
- Filtering by event_type, provider_id, and text search works
- Old events without new fields still render (graceful degradation)
- No breaking changes to existing API consumers
## Notes
- Storing provider_name/tracker_name as denormalized strings is intentional — the event log should be a historical record even if the tracker/provider is later deleted
- The `details` JSON field already exists and could hold extra data, but explicit columns are better for filtering
- EventLog.tracker_id already exists as FK — can join for backfill but also store name directly
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,43 +0,0 @@
# Phase 3: Richer Events Display + Filtering & Sorting
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Update the dashboard to display richer event details (provider name, tracker name, album name, event type, assets count) and add filtering/sorting controls.
## Tasks
- [ ] Task 1: Update event timeline items in `+page.svelte` to show: provider name, tracker name, album name, event type badge, and assets count
- [ ] Task 2: Add filter controls above the events list: text search input, event type dropdown (all/assets_added/assets_removed/collection_renamed/collection_deleted/sharing_changed), provider dropdown (populated from providers list)
- [ ] Task 3: Add sort control: newest first / oldest first toggle
- [ ] Task 4: Wire filters to API query params (event_type, provider_id, search) from Phase 2
- [ ] Task 5: Add "load more" button or increase default limit for events
- [ ] Task 6: Ensure graceful display when enriched fields are empty (old events before Phase 2)
## Files to Modify/Create
- `frontend/src/routes/+page.svelte` — event display, filter/sort controls, API calls
## Acceptance Criteria
- Each event shows: provider name, tracker name, album name, event type (badge), assets count
- Filtering by text search, event type, and provider works
- Sort by time (newest/oldest) works
- Old events without enriched data display gracefully (show what's available)
- Filter/sort state resets on page load (no persistence needed)
- UI is responsive and consistent with existing design
## Notes
- Depends on Phase 2's enriched API response
- Provider list for the dropdown can come from existing /api/providers endpoint
- Event type badges already have color mapping in the current dashboard code
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,50 +0,0 @@
# Phase 4: Friendly Chat Names + Test Message for Bots
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Two Telegram UX improvements: (a) show friendly chat names instead of raw IDs on target cards, (b) add a "Send Test Message" button to each chat item on the bots page.
## Tasks
### 4a: Friendly Chat Names on Target Cards
- [ ] Task 1: Update GET /api/targets in targets.py to resolve chat_id → friendly name by looking up TelegramChat table (match on bot token's bot_id + chat_id)
- [ ] Task 2: Include `chat_name` field in target API response alongside chat_id
- [ ] Task 3: Update target cards in targets/+page.svelte to display "Chat Name (chat_id)" instead of raw chat_id
### 4b: Test Message Button for Bot Chats
- [ ] Task 4: Add POST /api/telegram-bots/{bot_id}/chats/{chat_id}/test endpoint in telegram_bots.py that sends a simple test message via the bot
- [ ] Task 5: Add "Send Test" button to each chat item in telegram-bots/+page.svelte with loading/success/error feedback
- [ ] Task 6: Handle edge cases (bot can't reach chat, chat deleted, etc.) with proper error messages
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/api/targets.py` — resolve chat names
- `packages/server/src/notify_bridge_server/api/telegram_bots.py` — test message endpoint
- `frontend/src/routes/targets/+page.svelte` — display friendly names
- `frontend/src/routes/telegram-bots/+page.svelte` — test message button
## Acceptance Criteria
- Target cards show "Chat Title (chat_id)" for telegram targets where chat name is known
- Falls back to just chat_id when no matching TelegramChat record exists
- Test message button sends a simple "Test message from Notify Bridge" to the chat
- Button shows loading state, then success/error feedback
- Error messages are user-friendly (not raw API errors)
## Notes
- TelegramChat stores: chat_id (string), title, chat_type, username, bot_id (FK)
- NotificationTarget.config stores: bot_token, chat_id
- To resolve: need to find TelegramBot by token → get bot_id → lookup TelegramChat by (bot_id, chat_id)
- For test message: use TelegramClient directly with bot token from TelegramBot record
- The test endpoint on targets already exists (POST /api/targets/{id}/test) — this is a NEW endpoint specifically for bot chat items
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,48 +0,0 @@
# Phase 5: Album Public Link Validation
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
When saving/updating a tracker with album selections, check if the selected albums have valid public shared links. Warn the user about missing links and offer to auto-create them.
## Tasks
- [ ] Task 1: Add GET /api/providers/{id}/albums/{album_id}/shared-links endpoint that wraps ImmichClient.get_shared_links()
- [ ] Task 2: Add POST /api/providers/{id}/albums/{album_id}/shared-links endpoint that wraps ImmichClient.create_shared_link()
- [ ] Task 3: In trackers/+page.svelte, after album selection changes (on save), call shared-links endpoint for each newly selected album
- [ ] Task 4: Show a warning dialog/section listing albums without valid public links (expired, password-protected, or missing)
- [ ] Task 5: Add "Auto-create public links" button in the warning dialog that calls the create endpoint for each missing album
- [ ] Task 6: Add hints explaining implications: "Public links allow anyone with the URL to view album contents" and "Albums without public links will have limited notification features (no clickable links in messages)"
- [ ] Task 7: Allow user to proceed without creating links (dismiss warning and save anyway)
## Files to Modify/Create
- `packages/server/src/notify_bridge_server/api/trackers.py` or new `providers.py` routes — shared link endpoints
- `frontend/src/routes/trackers/+page.svelte` — validation UI, warning dialog, auto-create flow
## Acceptance Criteria
- On tracker save with new albums, shared links are checked
- Albums without valid links are highlighted with a warning
- User can auto-create links with one click
- User can dismiss and proceed without links
- Hints explain security/privacy implications
- Already-valid links are not re-created
- Expired or password-protected links are flagged as problematic
## Notes
- ImmichClient already has: get_shared_links(album_id), create_shared_link(album_id, ...), delete_shared_link(), set_shared_link_password()
- SharedLinkInfo model has: id, key, has_password, is_expired, is_accessible
- A "valid" link = exists AND not expired AND is_accessible (has_password is a warning, not a blocker)
- The check should only run for NEWLY selected albums (not all albums on every save)
- Use a modal/dialog for the warning — follows project convention of fixed-position overlays
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,47 +0,0 @@
# Phase 6: Support Albums Without Public Links
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Allow tracking albums even without public links. Templates should conditionally wrap items in `<a href>` when public URLs exist, otherwise show plain text names. Telegram should still send assets to chats regardless of public link status.
## Tasks
- [ ] Task 1: In template context building (context.py), include `has_public_url` / `album_url` / per-asset `public_url` flags so templates can conditionally render links
- [ ] Task 2: Update default system templates (EN/RU seeds) to use `{% if album_url %}` / `{% if asset.public_url %}` conditionals — wrap in `<a href>` when URL exists, plain text otherwise
- [ ] Task 3: Verify that Telegram notification sending (notifier.py + telegram client) sends assets via direct API download regardless of public link status (it should already work since it uses internal API URLs with api_key headers, not public links)
- [ ] Task 4: Ensure the template context correctly distinguishes between internal API URLs (for media download) and public URLs (for user-facing links in messages)
- [ ] Task 5: Test that events from albums without public links still generate notifications with asset media but no clickable links in the message text
## Files to Modify/Create
- `packages/core/src/notify_bridge_core/templates/context.py` — add public_url flags to context
- `packages/core/src/notify_bridge_core/providers/immich/provider.py` — ensure shared link info flows through events
- `packages/server/src/notify_bridge_server/services/notifier.py` — verify asset sending works without public links
- Default template seeds (wherever EN/RU templates are defined) — conditional link rendering
## Acceptance Criteria
- Albums without public links can be tracked without errors
- Notifications are sent with media assets regardless of public link status
- Message text includes clickable links only when public URLs exist
- Message text shows plain album/asset names when no public URL
- Default EN/RU templates handle both cases
- No regressions for albums that DO have public links
## Notes
- Internal asset URLs use format: `{provider_url}/api/assets/{id}/original` with x-api-key header
- Public URLs use format: `{external_domain}/share/{key}` (no auth needed)
- Telegram client downloads via internal URLs (with headers) — this is independent of public links
- The public URL is only relevant for the message text (human-readable links)
- Template context already has `album_url` from event.extra — need to make it None/empty when no shared link
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
+1 -1
View File
@@ -24,7 +24,7 @@ fi
# Start backend
export NOTIFY_BRIDGE_DATA_DIR=./test-data
export NOTIFY_BRIDGE_SECRET_KEY=test-secret-key-minimum-32chars
export NOTIFY_BRIDGE_SECRET_KEY=test-secret-key-minimum-32-chars
nohup "$PYTHON" -m uvicorn notify_bridge_server.main:app \
--host 0.0.0.0 --port 8420 > .backend.log 2>&1 &