Skip to content

feat: config backup & restore (export/import)#41

Open
bin101 wants to merge 5 commits into
mainfrom
feat/config-backup
Open

feat: config backup & restore (export/import)#41
bin101 wants to merge 5 commits into
mainfrom
feat/config-backup

Conversation

@bin101

@bin101 bin101 commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • GET /api/export β€” downloads a JSON file with config (cookie stripped), settings, athleteLabels, athleteAvatars, and a version/exported_at envelope. Response has Content-Disposition: attachment so the browser prompts a save dialog.
  • POST /api/import β€” validates both config and settings sections via Pydantic before writing. Cookie is only updated if explicitly present and non-empty in the payload β€” the existing live cookie is always preserved otherwise. Both writes go through the existing atomic store functions.
  • Frontend β€” Export button triggers a blob download (no page navigation). Import button opens a file picker; after the user selects a file the content is parsed and a native <dialog> confirmation modal is shown before anything is sent to the API. On success the UI reloads config + settings in-place.

Test plan

  • ruff check src tests β€” zero errors
  • ruff format --check src tests β€” zero issues
  • mypy src β€” zero type errors
  • pytest --cov=kudosy β€” 416 tests pass, 86% coverage
  • Click Export β†’ browser downloads kudosy-backup.json, cookie field absent
  • Open exported file, change intervalMinutes, click Import, confirm β†’ settings updated, cookie unchanged
  • Import a backup that contains stravaSessionCookie β†’ cookie updated
  • Import a backup that contains stravaSessionCookie: "" β†’ existing cookie preserved
  • Import a malformed JSON file β†’ toast error, no API call made
  • POST /api/import with missing config β†’ 422; missing settings β†’ 422

πŸ€– Generated with Claude Code

bin101 and others added 5 commits July 1, 2026 17:43
The 1450-line app.js is replaced by 12 focused ES modules:
state.js, dom.js, api.js, format.js, schedule-matrix.js,
athletes.js, config.js, settings.js, feed.js, status.js,
tabs.js, main.js.

The importmap in routes.py::serve_index now covers all local
modules so every module is cache-busted on release. Entry
point changes from app.js to main.js.

No behaviour change β€” all 380 tests pass, coverage 87 %.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a rolling run-history store (capped at 500 entries, newest-first) and
a Statistics tab that aggregates kudos per day, summary cards, and a
last-20-runs detail table rendered with a pure-canvas bar chart.

- store.py: append_run_history / read_run_history (atomic JSON writes)
- app.py: persist compact RunResult entry after every scheduler job
- routes.py: GET /api/history?limit= endpoint + ./stats.js in importmap
- static/stats.js: loadStats() + drawBarChart() (no CDN, CSP-safe)
- static/tabs.js: 'stats' added to validTabs; first-visit loadStats() call
- static/main.js: initStatsTab() wired in init()
- static/state.js: statsLoaded flag
- static/index.html: Statistics tab nav button + tab-pane (inside <main>)
- static/i18n.js: DE + EN keys for stats tab
- tests/unit/test_run_history.py: 7 unit tests (TDD)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add webhook notifications (ntfy/Telegram/any HTTP POST) and a visible
auth-error banner when the Strava session cookie expires.

Backend:
- notify.py: send_notification() with injected post_fn (testable without
  network); build_run_payload() and build_auth_error_payload() helpers
- models.py: AppSettings + notifyWebhookUrl (http/https validated),
  notifyOnRun, notifyOnAuthError; RunStatus + authOk (bool | None)
- app.py: _run_job tracks auth_ok state, sends webhook on run-complete
  (if notifyOnRun) and on AuthError (if notifyOnAuthError); AuthError
  is now caught so a bad cookie no longer leaves the scheduler broken
- routes.py: GET /api/status includes authOk from _app_state

Frontend:
- index.html: auth-error banner (hidden by default) + Notifications
  settings card (webhook URL, onRun toggle, onAuthError toggle)
- status.js: show/hide banner based on s.authOk === false
- settings.js: load/save notifyWebhookUrl, notifyOnRun, notifyOnAuthError
- i18n.js: DE/EN/FR/ES/IT keys for notify settings + status.authError
- styles.css: .auth-error-banner styling (error-bg / error colors)

Tests:
- tests/unit/test_notify.py: 13 unit tests (TDD β€” written first)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add GET /api/export and POST /api/import so users can download and
restore their config + settings without touching the session cookie.

Backend:
- routes.py: GET /api/export β†’ JSON attachment (cookie stripped, includes
  config, settings, athleteLabels, athleteAvatars); POST /api/import β†’
  validates both sections via Pydantic, preserves existing cookie when
  absent/empty in the payload, writes config + settings atomically
- BaseModel imported in routes.py for the _ImportBody request model

Frontend:
- backup.js: triggerExport() (blob download), handleFileSelect() (JSON
  parse + validation), openImportDialog() / confirmImport() with native
  <dialog> element; dynamic import of config.js/settings.js to reload
  UI after a successful import
- index.html: Backup & Restore section (Export button, Import button,
  hidden file input, <dialog> confirmation modal) between the config and
  automation sections
- main.js: initBackup() wired in init()
- routes.py importmap: ./backup.js added for cache-busting
- i18n.js: DE/EN/FR/ES/IT keys for all backup UI strings
- styles.css: native <dialog> styling + ::backdrop

Tests:
- tests/api/test_backup.py: 16 API tests (TDD β€” written first)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant