# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.1.0] - 2026-05-19 ### Added - Auth: E-Mail-Verifikation bei Registrierung — Token (SHA-256-Hash in DB, 24h gültig) wird per Mail versendet; `/verify-email?token=` Frontend-Route löst automatisch `POST /api/auth/verify-email/` aus - Auth: Passwort vergessen / Reset — `POST /api/auth/password-reset/` (anti-enumeration); `POST /api/auth/password-reset/confirm/` setzt Passwort und invalidiert alle aktiven Sessions; Token (SHA-256-Hash, 15min TTL) via Brevo-Mail mit Link - Auth: `ForgotPassword`-Komponente (`/forgot-password`), `ResetPassword`-Komponente (`/reset-password`), `VerifyEmail`-Komponente (`/verify-email`) - Auth: "Passwort vergessen?"-Link auf Login-Seite - E-Mail-Templates: `registration_confirm`, `password_reset`, `password_changed`, `email_changed` (je HTML + Plaintext) - Backend: `finance/email.py` — generischer `send_email()` Helper mit `EmailMultiAlternatives` - Backend: `FRONTEND_URL` Env-Var für absolute Links in Mails; `EMAIL_BACKEND` via Env-Var überschreibbar - i18n: `auth.forgot_password`, `auth.reset_password`, `auth.new_password`, `auth.email_verified` + Error-Keys (DE/EN/FR/IT) - Feature: Jahresplanung (`/financial-year`) — Jahres-Dropdown, 3 Summary-Cards, Tabs Einnahmen/Fixkosten mit Inline-CRUD; "Neues Jahr starten" (max. 1 Jahr im Voraus) - Backend: `FinancialYear`, `YearlyIncome`, `YearlyBudgetItem`, `Household`, `HouseholdMembership` Modelle (Migration 0019) - Backend: vollständige REST-API für Jahresplanung und Haushalte inkl. Invite/Accept/Leave/SetRole - Backend: Django Management Command `migrate_to_financial_year` (idempotent, `--dry-run`) - Frontend: `FinancialYearService` mit allen Typen und API-Methoden - Frontend: Household-Sektion auf `/financial-year` (Gründen, Einladen, Rollen, Annehmen, Verlassen) - Sidebar: "Jahresplanung" Nav-Item - Dashboard: Einnahmen/Fixkosten aus `FinancialYearService` statt Account/Budget-Daten - Dashboard: Einnahmen vs. Ausgaben — Flowbite-Redesign, 3 Serien, Jahres-Dropdown - Dashboard: Fixkostenaufschlüsselung — Pie Chart mit %-Labels, Toggle zur Listenansicht - Dashboard: Sparquote — personalisierbarer Ziel-Marker, Settings-Toggle zum Anpassen - Security: Cloudflare Turnstile CAPTCHA auf Login + Register - Infrastructure: Brevo SMTP (`smtp-relay.brevo.com:587`), Domain `armarium.ch` verifiziert (SPF/DKIM) - i18n: `sidebar.financial_year`, `financial_year.*`, `dashboard.*`, `auth.errors.captcha_failed` (DE/EN/FR/IT) - Settings: Active Sessions card — lists all logged-in devices (device name, IP, last active); individual revoke and "sign out all others" buttons; current session marked with badge; `UserSession` model with `session_key`, `refresh_jti`, `device_name`, `ip_address`; `_create_session()` called on every successful login (including 2FA and recovery flows) - Settings: Data Export — downloads a ZIP containing 6 structured PDFs (Profil, Konten, Budgets, Ausgaben, Transaktionen, Termine) generated server-side with fpdf2; violet header bar, alternating row fill, gray footer with page numbers - Settings: Notification Preferences — toggles for "Anstehende Termine", "Budget-Warnungen", "Monatliche Zusammenfassung"; saved via `PATCH /api/notifications/prefs/`; loaded from profile on page open - Settings: Account deletion now requires two steps — (1) mandatory data export, (2) confirmation form with password field (show/hide eye icon) and translated confirmation phrase (`profile.delete_account` in current language); delete button disabled until both conditions met - Settings: After account deletion, user is redirected to `https://www.armarium.ch` and both storages are cleared - Auth: Language switcher (`LangSwitcher` component) inside the login and register cards (top-left); uses `@HostListener` for outside-click close and `[class]` binding to avoid Tailwind dark-mode colon conflicts - Auth: "Angemeldet bleiben" checkbox on login with two-line label; toggles between `localStorage` (persistent) and `sessionStorage` (session-only); persisted through full 2FA and recovery flows via `keepSignedIn` parameter - Auth: `session_key` stored alongside JWT tokens in same storage; sent as `X-Session-Key` header by interceptor so backend can identify current session - Backend: `PATCH /api/notifications/prefs/` endpoint (`NotificationPrefsView`) - Backend: `GET /api/auth/sessions/`, `DELETE /api/auth/sessions//`, `DELETE /api/auth/sessions/revoke-all/` endpoints - Backend: `GET /api/export/` returns ZIP with 6 PDFs via fpdf2 - Backend: Admin URL configurable via `ADMIN_URL` env var (default `manage/`); obscures the standard `/admin/` path from scanners - Backend: SMTP email config from env vars (`EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD`, `EMAIL_USE_TLS`, `DEFAULT_FROM_EMAIL`); console backend in DEBUG mode - Backend: production security block (`if not DEBUG`) — `SECURE_SSL_REDIRECT`, HSTS (1 year, preload, subdomains), secure cookies, `SECURE_CONTENT_TYPE_NOSNIFF`, proxy SSL header - Backend: `DATA_UPLOAD_MAX_MEMORY_SIZE` and `FILE_UPLOAD_MAX_MEMORY_SIZE` capped at 5 MB - Backend: `CSRF_TRUSTED_ORIGINS` from env var - i18n: `settings.sessions_*`, `settings.export_*`, `settings.notif_*`, `settings.delete_*` keys (DE/FR/IT/EN) - i18n: `auth.keep_signed_in`, `auth.keep_signed_in_hint`, `auth.back_to_login` keys (DE/FR/IT/EN) - Security: TOTP-based two-factor authentication (2FA) — setup via QR code, verified on login; production-ready with HMAC-signed temp token (5 min expiry), replay protection via `totp_last_used_code`, and 8 backup codes in `XXXXXXXX-XXXXXXXX` format (SHA-256 hashed in DB) - Security: `TwoFactorSetupView`, `TwoFactorEnableView`, `TwoFactorDisableView`, `TwoFactorLoginView` backend endpoints; `BackupCode` model with index on `(user, used)` - Settings page (`/settings`): 2FA card (enable/disable, QR scan, backup code copy + PDF download) and Danger Zone (account deletion); accessible from navbar avatar menu - Login: two-step flow — step 1 credentials, step 2 TOTP/backup code entry when 2FA is active - Backup codes: copy to clipboard and PDF download via jsPDF (client-side, no server round-trip) - Dashboard: donut chart "Fixed Costs Breakdown" now shows individual budget entry names and amounts (was: grouped by category) - Dashboard: toggle button (top-right of donut card) switches between chart view and a scrollable breakdown list with color dot, name, CHF amount and percentage per entry - i18n: `auth.totp_title`, `auth.totp_hint`, `auth.totp_or_backup`, `auth.back_to_login`, `auth.errors.invalid_totp` keys (DE/FR/IT/EN) - i18n: `profile.totp_*`, `profile.backup_*` keys for all 2FA labels, backup codes and messages (DE/FR/IT/EN) - i18n: `settings.subtitle` key (DE/FR/IT/EN) ### Changed - `.env.example`: `FRONTEND_URL` und `EMAIL_BACKEND` Variablen ergänzt - Profile-Model: `email_verify_token` neu als SHA-256-Hash gespeichert; `email_verify_token_expires` (24h TTL) hinzugefügt (Migration 0020) - **UI:** migrated frontend to Flowbite design system — custom Tailwind v4 theme (`budget-app-theme.css`) with violet primary color, consistent rounded-lg cards, Flowbite outline SVG icons throughout - **Navbar:** avatar dropdown and notification panel converted from Flowbite JS (`data-dropdown-toggle`) to Angular state management (`avatarDropdownOpen` signal + backdrop `
` for outside-click close); eliminates Flowbite JS runtime dependency - **Navbar:** sun/moon/bell icons replaced with Flowbite outline variants; logout entry in avatar dropdown now shows `arrow-right-to-bracket` icon - **Sidebar:** navigation icons restored to fill style (`fill="currentColor" viewBox="0 0 20 20"`) for consistency with pre-migration appearance; Settings link added to mobile drawer after Profile link - **Dashboard:** greeting H1 changed to `font-light` (weight 300) - **Mobile — tables:** non-critical columns hidden on small screens (`hidden sm:table-cell` for category, `hidden md:table-cell` for account) in expense and transaction lists - **Mobile — touch targets:** all icon-only buttons (edit, delete, modal close, calendar navigation arrows) updated to `p-2` minimum (was `p-1.5`) - **Mobile — form grids:** `grid-cols-2` changed to `grid-cols-1 sm:grid-cols-2` in expense modals and profile form - **Mobile — budget entries:** `min-w-0 flex-1` and `truncate` on name/account spans prevent overflow on narrow screens - **Mobile — calendar:** day cells `min-h-[48px] sm:min-h-[64px]`, day detail drawer `w-full sm:w-80` - **Mobile — dashboard:** KPI card padding `p-3 sm:p-5`, KPI values `text-xl sm:text-2xl` - **Mobile — login:** OTP digit inputs `h-10 w-10 sm:h-12 sm:w-12`; backup code field placeholder removed (hint text above suffices) - **Mobile — settings:** recovery email row `flex-col sm:flex-row` so button stacks below input on narrow screens - **Search:** placeholder text styled `placeholder-gray-400` (was unstyled) - Typography: Roboto font self-hosted via `@fontsource/roboto` (300/400/500/700 weights) — no Google Fonts CDN, DSGVO/nDSG compliant; replaces Inter which was defined in the theme but never actually loaded - Typography: unified font-size system — page title H1 `text-2xl` (24px), card/section H2 headers `text-base` (16px), sidebar navigation `text-sm` (14px), savings rate display `text-3xl` (30px); applied across Dashboard, Budgets, Expenses, Calendar, Profile, Settings and Sidebar - Profile: password change fields no longer show browser autofill suggestions (`autocomplete="new-password"`); show/hide eye icon added to both password fields - Sidebar: version number updated to 1.1.0 - Login: registration is email-only — username field removed; backend auto-sets `username=email`; `RegisterSerializer` updated accordingly - Login: font sizes increased throughout login/register flow (`text-xs`→`text-sm`, `text-sm`→`text-base`, card `max-w-sm`→`max-w-md`) - Settings: 2FA card and Danger Zone remain; Recovery Email, Active Sessions, Data Export, Notification Preferences added above Danger Zone - Settings: Danger Zone delete flow is now three-step (export → credentials + phrase → redirect) - `LogoutView`: added `permission_classes = [AllowAny]` so logout works even when access token is expired; now also deletes `UserSession` by JTI and `X-Session-Key` - `ChangePasswordView`: invalidates and blacklists all other active sessions on password change - `authInterceptor`: sends `X-Session-Key` header on all internal API requests - Auth: `completeLogin()` and `storeTokens()` accept optional `session_key`; `logout()` clears session key from both storages - Auth: `refreshToken()` bug fixed — was storing `tokens.access` twice instead of `tokens.access` and `tokens.refresh` - Login endpoint `POST /api/auth/token/` replaced with custom `LoginView` supporting 2FA challenge response - Profile page: 2FA and Danger Zone sections removed and moved to new Settings page - Authenticator app recommendation updated to: Proton Pass, Aegis (Android), Raivo OTP (iOS) - Calendar: holidays and school holidays now shown in the current app language; reloaded automatically on language change via `translate.onLangChange` subscription - Calendar: OpenHolidays API requests include `languageIsoCode` parameter; cache key includes language - Calendar: today's date shown as violet ring/outline only (not filled) to prevent white-on-white hover text - Notifications: "Mark as read" checkmark button per notification (replaces X icon) - Notifications: "Mark all as read" button in panel header - i18n: `nav.mark_read`, `nav.mark_all_read` keys (DE/FR/IT/EN) - Calendar: live holiday and school holiday data via OpenHolidays API (openholidaysapi.org, AGPL-3.0) with automatic fallback to static data on API failure - Calendar: in-memory cache per year/canton to avoid redundant API requests - Budgets: show info modal when no accounts exist, with link button to accounts page - Expenses: show info modal when no accounts exist, with link button to accounts page - i18n: `common.no_accounts_title`, `common.no_accounts_text`, `common.go_to_accounts` keys (DE/FR/IT/EN) - Login: authentication changed from username-based to email-based; password manager only needs email + password - Mobile sidebar drawer: Notifications, Dark/Light toggle, Profile link and Logout moved from navbar into the sidebar so all user actions are accessible via the hamburger menu ### Security - Password Reset invalidiert alle aktiven Sessions des Nutzers (analog zu `ChangePasswordView`) - Email-Verify-Token: SHA-256-Hash statt Klartext in DB; Ablaufzeit 24h - `VerifyEmailView` + `PasswordResetConfirmView` mit `AuthThrottle` (5/min) gesichert ### Fixed - Security: `TwoFactorRecoverConfirmView` now verifies `temp_token` (password proof) before accepting a recovery code — previously anyone with a valid recovery code could bypass authentication entirely - Security: `ProfileView.delete` now requires password verification (`check_password`) before deletion; returns 403 on failure - Backend: URL conflict — `api/notifications/` was mapped to both `NotificationsView` and `NotificationPrefsView`; prefs endpoint moved to `api/notifications/prefs/` - Backend: fpdf2 export — em/en dash characters (`—`, `–`) caused `FPDFUnicodeEncodingException` with Helvetica (Latin-1 only); fixed with `safe()` helper using `encode('latin-1', errors='replace')` and replaced placeholder dashes with ASCII `-` - Backend: migration 0017 (`finance_profile.notif_deadlines` etc.) was not applied on server restart, causing 500 on login; now documented that `python manage.py migrate` must be run after deployment - Auth: `LangSwitcher` — `[class.dark:text-violet-400]` Angular binding error caused by Tailwind dark-mode colon in class name; fixed by using `itemClass()` method with `[class]` binding - Auth: `LangSwitcher` — `current` signal initialized before `langService` was available; fixed by setting default `'de'` and calling `this.current.set(langService.current)` in constructor body - Dashboard: bar chart right padding increased so December bars are no longer clipped at the container edge - Calendar: legend block removed from footer - Calendar: ZH Spring Holidays 2026 corrected to 20.04.–02.05. in static fallback data (source: OpenHolidays API) - Calendar: date input now renders in the app language format (lang attribute bound to current language) - Calendar: deadline type dropdown now shows placeholder on open; defaults to "other" if none selected - Calendar: title input placeholder text styled correctly in gray - Login: show/hide password toggle with eye icon - Register: show/hide password toggle on both password fields, independently toggleable - Register: hint text below username field explaining it is used as in-app display name (DE/FR/IT/EN) - i18n: `nav.dark_mode`, `nav.light_mode` keys for sidebar mobile theme toggle (DE/FR/IT/EN) - i18n: `auth.username_hint` key for register page (DE/FR/IT/EN) - Mobile navbar: right-side icons (notifications, theme toggle, avatar) caused the navbar to wrap to a second line on narrow screens, pushing page content below the fixed offset; icons are now hidden on mobile and integrated into the sidebar drawer - Mobile notification panel: was full-width and flush against the top of the screen; now has `left-4`/`right-4` margins, `top-20` offset and `rounded-xl` corners - Auth: new `EmailAuthBackend` performs case-insensitive email lookup so login works regardless of capitalisation - Login/Register: placeholder texts removed from all username and password fields --- ## [1.0.1] - 2026-04-14 ### Fixed - Production deployment: replaced hardcoded `http://127.0.0.1:8000` with relative paths (`/api`, `/api/auth`) in `ApiService` and `AuthService` so the frontend works on any server - Production deployment: avatar image URLs in `profile.ts` and `navbar.ts` now use the relative path returned by the backend instead of prepending `http://127.0.0.1:8000` - Sidebar: Budgets and Accounts submenus not expanding on mobile — replaced Flowbite `data-collapse-toggle` with Angular signal-based state (`budgetsOpen`, `accountsOpen` in `SidebarService`) ### Added - `nginx.conf`: reverse proxy config — serves Angular static build, proxies `/api/` to Gunicorn on port 8000, serves `/media/` as static files --- ## [1.0.0] - 2026-04-13 ### Added - Branding: app renamed to "Armarium"; browser tab title updated - Branding: `Logo_horizontal.svg` in navbar, `Logo_vertikal.svg` on login/register, `Icon.svg` as favicon - Branding: navbar logo inverts colors in dark mode via `dark:invert` - Responsive layout: mobile-first redesign across all pages - Responsive layout: sidebar becomes a slide-in overlay drawer on mobile (hamburger in navbar) - Responsive layout: mobile backdrop closes sidebar on outside click or navigation - Responsive layout: main content padding reduced on mobile (`p-4 lg:p-8`), sidebar margin shifted to `lg:` breakpoint - Responsive layout: tables (accounts, expenses, transactions) get `min-w` and `overflow-x-auto` for horizontal scroll on mobile - Responsive layout: notifications panel becomes a full-width top drawer on mobile (`fixed top-[57px] left-0 right-0`), dropdown on desktop - Responsive layout: desktop sidebar toggle button hidden on mobile (`hidden lg:flex`) - Database: migrated from SQLite to PostgreSQL; connection configured via `.env` variables (`DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT`) - Database: added `psycopg2-binary` as PostgreSQL adapter; added `.env.example` for onboarding - i18n: full 4-language support (DE/FR/IT/EN) via ngx-translate; translation files in `assets/i18n/` - i18n: language selector in Profile settings, persisted to backend `Profile.language` field and `localStorage` - i18n: browser language auto-detection on Login and Register pages - i18n: `LanguageService` for centralised language init, detection and switching - i18n: canton names translated per UI language via `canton_names` keys; ZH = "Zürich" (DE), "Zuerich" (EN), "Zurigo" (IT), "Zurich" (FR) - Calendar year view: clicking a month card opens a modal with an enlarged month view - Sidebar collapse toggle in navbar: sidebar can be collapsed to icon-only view and expanded again - Collapsed sidebar: flyout submenus for Budgets and Accounts with backdrop to close on outside click - Collapsed sidebar: tooltips on hover for all navigation icons - Dark/Light mode toggle in navbar with sun/moon icons; preference persisted in localStorage (dark mode default) - iCal feed: `GET /api/calendar/ical-url/` returns personal iCal feed URL per user - iCal feed: `GET /api/calendar/ical///` serves `.ics` file with deadlines and expense due dates (HMAC-SHA256 token auth) - Calendar: "Subscribe" button in header opens popup with iCal feed URL and copy button ### Changed - Calendar day detail: replaced bottom panel with a slide-in drawer from the right side - All UI strings replaced with i18n translation keys (DE/FR/IT/EN) across all pages and components ### Fixed - Profile: canton and language selects not saving — replaced `[value]`/`(change)` with `[ngModel]`/`(ngModelChange)` for reliable Angular select binding - Calendar: canton selector not reflecting the profile canton on load - Sidebar collapse button was not wired to any action - Sidebar flyout submenus were clipped inside collapsed sidebar due to `overflow-y-auto` - iCal feed: removed unused `pytz` import that caused `ModuleNotFoundError` - i18n: translations not loading due to `src/assets` missing from `angular.json` asset sources --- ## [0.1.0] - 2026-03-08 ### Added #### Backend (Django) - Initialized Django project with `core` configuration app and `finance` app - Configured SQLite3 as development database - Integrated Django REST Framework with JWT authentication via `djangorestframework-simplejwt` - Enabled JWT token blacklist for secure logout and token rotation - Configured `django-cors-headers` to allow requests from Angular frontend (`localhost:4200`) - Implemented rate limiting: 5/min (auth), 200/min (authenticated users), 20/min (anonymous) - Configured environment variables via `python-dotenv` (SECRET_KEY, DEBUG, ALLOWED_HOSTS, CORS) - Added media file handling for avatar image uploads #### Data Models - `Account` – asset, expense, and revenue account types with balance tracking - `Transaction` – double-entry bookkeeping with source and destination accounts - `Budget` – 7 categories (fixed expenses, mobile/internet, subscriptions, leisure, tax reserves, insurance, loans) - `Expense` – 10 categories with optional due date and notes - `Profile` – user profile with avatar (image + color), name, email, and canton (all 26 Swiss cantons) - `Deadline` – 5 types (tax, insurance, invoice, personal, other) #### REST API - `POST /api/auth/register/` – user registration - `POST /api/auth/token/` – JWT login - `POST /api/auth/token/refresh/` – token refresh - `POST /api/auth/logout/` – logout with token blacklist - `POST /api/auth/password/` – password change - `GET/POST/PUT/DELETE /api/accounts/` – account management - `GET/POST/PUT/DELETE /api/transactions/` – transaction management - `GET/POST/PUT/DELETE /api/budgets/` – budget management - `GET/POST/PUT/DELETE /api/expenses/` – expense management - `GET/POST/PUT/DELETE /api/deadlines/` – deadline management - `GET/PUT/DELETE /api/profile/` – user profile with avatar upload #### Frontend (Angular) - Initialized Angular 21.2 project using standalone component architecture - Integrated Tailwind CSS v3.4 for utility-first styling - Integrated Flowbite 4.0.1 as UI component library (initialized via `initFlowbite()` in shell) - Integrated ApexCharts 3.46.0 for data visualization - Added `typings.d.ts` for Flowbite TypeScript module declaration - Implemented `ApiService` for all HTTP communication with the Django REST API - Implemented `AuthService` for JWT token storage and management - Implemented `authGuard` to protect routes requiring authentication - Implemented `authInterceptor` to attach Bearer token to all outgoing requests and handle 401 errors - Configured `SidebarService` for sidebar open/close state management #### Pages & Components - Login and Register pages with JWT-based authentication - Shell layout with responsive sidebar and navbar (user avatar, canton display) - Dashboard with KPIs (total income, fixed costs, expenses, available balance, savings rate) and ApexCharts bar and donut charts - Accounts page with full CRUD for asset, expense, and revenue accounts - Budgets page with category grouping and budget suggestions per category - Expenses page with category filtering and due date tracking - Transactions page with double-entry transaction management - Calendar page with year and month view, Swiss public holidays and school holidays by canton (2025–2026), expense due dates and personal deadlines - Profile page with avatar upload, color selection, name, email, and canton selection