feat: comprehensive code review fixes + receivers-only architecture
Security:
- Refuse startup with default secret_key in production (was just logging)
- Settings endpoint now requires admin role
- Password validation on initial setup
- DOM-based HTML sanitizer replaces regex in template previews
- Add *.log to .gitignore
Performance & reliability:
- Token refresh deduplication prevents race condition on concurrent 401s
- Theme media query listener registered once (no leak)
- IconPicker uses $derived instead of function call per render
- Snackbar uses single-batch state update instead of while loop
- Replace 11 inline hover handlers with CSS :hover in layout
Architecture - receivers-only:
- Delivery endpoints (chat_id, email, url, room_id, topic) now stored
exclusively in TargetReceiver rows, never in target.config
- Migration extracts existing delivery fields to receiver rows
- Notifier and dispatcher remove all config fallbacks
- Frontend targets page shows receivers list per target with
add/remove/toggle/test per receiver
- Single-receiver test endpoint: POST /targets/{id}/receivers/{id}/test
Code quality:
- Extract AuthLayout.svelte from login/setup (150 lines CSS dedup)
- Split telegram-bots page (754→51 lines + 3 tab components)
- Split notification-trackers page (547→432 lines + 4 components)
- Deduplicate _send_reply into shared handler.send_reply()
- Add locale column to template models, replace name-based detection
- Fix delete_notification_tracker dead protection check
- Fix check_telegram_bot query (filter by type, remove bogus OR)
- Add graceful scheduler shutdown in lifespan
- Consistent /bots?tab=telegram URLs across all nav links
i18n:
- Error page, chat actions, target types, provider types internationalized
- All new receiver UI strings in EN + RU
This commit is contained in:
@@ -39,13 +39,15 @@ async def lifespan(app: FastAPI):
|
||||
await init_db()
|
||||
# Run data migrations (idempotent)
|
||||
from .database.engine import get_engine
|
||||
from .database.migrations import migrate_schema, migrate_tracker_targets, migrate_entity_refactor, migrate_template_slots, migrate_target_receivers
|
||||
from .database.migrations import migrate_schema, migrate_tracker_targets, migrate_entity_refactor, migrate_template_slots, migrate_target_receivers, migrate_template_locale, migrate_receivers_from_config
|
||||
engine = get_engine()
|
||||
await migrate_schema(engine)
|
||||
await migrate_tracker_targets(engine)
|
||||
await migrate_entity_refactor(engine)
|
||||
await migrate_template_slots(engine)
|
||||
await migrate_target_receivers(engine)
|
||||
await migrate_template_locale(engine)
|
||||
await migrate_receivers_from_config(engine)
|
||||
await _seed_default_templates()
|
||||
await _seed_default_command_templates()
|
||||
# Configure webhook secret from DB setting (falls back to env var)
|
||||
@@ -54,9 +56,13 @@ async def lifespan(app: FastAPI):
|
||||
async with _AS(engine) as _session:
|
||||
_secret = await _get_setting(_session, "telegram_webhook_secret")
|
||||
set_webhook_secret(_secret or None)
|
||||
from .services.scheduler import start_scheduler
|
||||
from .services.scheduler import start_scheduler, get_scheduler
|
||||
await start_scheduler()
|
||||
yield
|
||||
# Graceful shutdown
|
||||
scheduler = get_scheduler()
|
||||
if scheduler.running:
|
||||
scheduler.shutdown()
|
||||
|
||||
|
||||
app = FastAPI(title="Notify Bridge", version="0.1.0", lifespan=lifespan)
|
||||
@@ -108,7 +114,8 @@ async def _seed_default_templates():
|
||||
)
|
||||
system_configs = result.all()
|
||||
existing_locales = {
|
||||
"ru" if "(RU)" in c.name else "en": c for c in system_configs
|
||||
(c.locale if c.locale else ("ru" if "(RU)" in c.name else "en")): c
|
||||
for c in system_configs
|
||||
}
|
||||
|
||||
for locale in ("en", "ru"):
|
||||
@@ -144,6 +151,8 @@ async def _seed_default_templates():
|
||||
values[col] = "%d.%m.%Y, %H:%M UTC"
|
||||
elif col == "date_only_format":
|
||||
values[col] = "%d.%m.%Y"
|
||||
elif col == "locale":
|
||||
values[col] = locale
|
||||
else:
|
||||
values[col] = "" # empty string for legacy columns
|
||||
cols_str = ", ".join(values.keys())
|
||||
@@ -211,6 +220,7 @@ async def _seed_default_command_templates():
|
||||
provider_type="immich",
|
||||
name=name,
|
||||
description=f"Default Immich command templates ({locale.upper()})",
|
||||
locale=locale,
|
||||
)
|
||||
session.add(config)
|
||||
await session.flush()
|
||||
@@ -227,7 +237,7 @@ async def _seed_default_command_templates():
|
||||
)
|
||||
system_configs = result.all()
|
||||
for config in system_configs:
|
||||
locale = "ru" if "(RU)" in config.name else "en"
|
||||
locale = config.locale if config.locale else ("ru" if "(RU)" in config.name else "en")
|
||||
slots = load_default_command_templates(locale)
|
||||
if not slots:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user