350ef6288d
Automatically generated by python-semantic-release
282 lines
30 KiB
Markdown
282 lines
30 KiB
Markdown
# 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.0.0/),
|
||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
<!-- version list -->
|
||
|
||
## v0.3.0 (2026-03-28)
|
||
|
||
### Features
|
||
|
||
- Mailbox-centric activity view, reduce log noise from empty polling cycles
|
||
([`dd4532f`](https://github.com/christianlouis/InboxConverge/commit/dd4532f6604e2e8c1fa27fd3da05cd5e62733842))
|
||
|
||
|
||
## v0.2.2 (2026-03-28)
|
||
|
||
### Bug Fixes
|
||
|
||
- Resolve semantic-release CHANGELOG.md not updating properly
|
||
([`d3e0ca4`](https://github.com/christianlouis/InboxConverge/commit/d3e0ca4e33779372b53884e25cf0d5cc3478848c))
|
||
|
||
### Chores
|
||
|
||
- **deps**: Bump cryptography
|
||
([`2fd018a`](https://github.com/christianlouis/InboxConverge/commit/2fd018afa109a2fa9d087da8590323c9a9d520ed))
|
||
|
||
|
||
## [Unreleased]
|
||
|
||
### Changed
|
||
- **Mailbox Activity view**: The user-facing "Logs" page has been redesigned to a mailbox-centric
|
||
layout (renamed "Mailbox Activity"). Each mail account is shown as a card with its last check
|
||
status and error (if any). Only runs that actually fetched emails are shown in the pull history,
|
||
eliminating noise from empty polling cycles. This mirrors Gmail's external POP pull UI.
|
||
- **Processing runs filter**: Added `has_emails` query parameter to `GET /processing-runs` and
|
||
`GET /mail-accounts/{id}/processing-runs`. When `has_emails=true`, only runs with
|
||
`emails_fetched > 0` are returned, allowing clients to suppress empty polling noise.
|
||
|
||
### Fixed
|
||
- **Processing log durations**: Runs that were killed by SIGKILL or failed before updating their
|
||
own status (e.g. missing SMTP credentials) now always record a correct `completed_at` and
|
||
`duration_seconds`. The error handler no longer accesses an expired SQLAlchemy ORM attribute
|
||
(`run.started_at`) after a session rollback, which previously caused the handler to crash and
|
||
left runs stuck in the `running` state indefinitely.
|
||
- **Celery datetime crash**: Fixed `TypeError: can't subtract offset-naive and offset-aware
|
||
datetimes` in `process_all_enabled_accounts` by wrapping `account.last_check_at` with the
|
||
existing `_as_utc()` helper before comparing against `datetime.now(timezone.utc)`. This
|
||
crash silently prevented every mail account from being processed on every scheduled run.
|
||
- **Per-account polling interval**: Changed the Celery beat schedule for
|
||
`process_all_enabled_accounts` from every 5 minutes (`*/5`) to every minute (`*`). The
|
||
per-account `check_interval_minutes` field already gates whether an account actually gets
|
||
processed, so accounts configured with a 1-minute interval are now polled as expected instead
|
||
of being limited to 5-minute effective intervals.
|
||
- **Processing log early-exit path**: When a mail account has no delivery method configured
|
||
(SMTP credentials missing and Gmail API not set up), the processing run now correctly sets
|
||
`completed_at`, `duration_seconds`, and `account.last_check_at`, preventing the account from
|
||
being re-queued on every scheduler tick and generating a flood of failed runs.
|
||
- **Stale-run detection moved to scheduler**: `process_all_enabled_accounts` (runs every 5 min)
|
||
now marks orphaned `running` runs as `failed` immediately. Previously this only happened in the
|
||
daily `cleanup_old_logs` task, meaning stale runs could show huge durations (hours/days).
|
||
- **Duration display rounding bug**: `formatDuration` in the frontend Processing Logs page now
|
||
uses `Math.floor` instead of `Math.round` for the seconds component, eliminating the "60s"
|
||
artefact that appeared for durations very close to a whole minute boundary.
|
||
|
||
### Changed
|
||
- **Decoupled Gmail permissions from Google Sign-In**: The "Sign in with Google" OAuth flow now only requests basic profile scopes (`openid`, `email`, `profile`) instead of also requesting Gmail API scopes (`gmail.insert`, `gmail.labels`, `gmail.readonly`). Users can grant Gmail access separately via the "Connect Gmail" button in Settings. This results in a simpler, permission-free login experience.
|
||
|
||
|
||
## [0.2.1] - 2026-03-27
|
||
|
||
### Fixed
|
||
- **`SyntaxWarning` at startup**: Fixed invalid escape sequence `\S` in a docstring in `mail_processor.py` (changed to `\\S`). In Python 3.12+ this emits a `SyntaxWarning` and will become a `SyntaxError` in a future Python version.
|
||
- **Processing runs stuck in "running" state**: Fixed three related bugs in `tasks.py` that caused `ProcessingRun` records to remain in the `running` state indefinitely:
|
||
1. The exception handler now calls `await db.rollback()` before attempting to write the `failed` status, ensuring the SQLAlchemy session is in a clean state even when the original exception occurred during a DB flush/commit.
|
||
2. The error-handler `await db.commit()` is now wrapped in its own `try/except` so a commit failure inside the handler no longer propagates silently and leaves the run as `running`.
|
||
3. `account.last_check_at` is now updated in the error path, throttling re-dispatch by `process_all_enabled_accounts` and preventing a cascade of new `running` runs on every scheduler tick.
|
||
- **Stale "running" run cleanup**: `cleanup_old_logs` now marks any `ProcessingRun` that has been in the `running` state for longer than 35 minutes (Celery hard time-limit is 30 min) as `failed` with an explanatory message. This recovers runs left behind by OOM kills, container restarts, or other SIGKILL events.
|
||
|
||
## [0.2.0] - 2026-03-27
|
||
|
||
### Added
|
||
- **Automatic release numbers** (`pyproject.toml`): Added `version_toml = ["pyproject.toml:project.version"]` to `[tool.semantic_release]` so that `python-semantic-release` now writes the computed version back into the `project.version` field of `pyproject.toml` on every release. The field is initialised to `0.0.0` and will be bumped automatically from that point forward.
|
||
- **Release badge** (`README.md`): Added a dynamic "GitHub Release" shield that always shows the latest published release tag.
|
||
- **Releases section** (`README.md`): Added a "Releases" section explaining the Semantic Versioning / Conventional Commits workflow and the version-bump rules.
|
||
|
||
### Fixed
|
||
- **CI `update-k8s-manifest` job**: Fixed image tag computation and `yq` update patterns to target `registry.cklnet.com` (private registry) instead of `ghcr.io`. The k8s manifest uses private registry image references, so the previous GHCR-based patterns never matched and no tag updates were applied.
|
||
- **CI `update-k8s-manifest` job**: Enhanced the PAT validation step to verify the token actually has read access to the `k8s-cluster-state` repository (via a GitHub API probe) before attempting checkout, preventing a 403 "Write access to repository not granted" failure when the PAT exists but lacks the necessary repository access.
|
||
|
||
## [0.1.2] - 2026-03-27
|
||
|
||
### Fixed
|
||
- **CI `update-k8s-manifest` job**: Added a `Check if GH_PAT is configured` step that emits a warning and skips the GitOps steps when the `GH_PAT` secret is absent or empty, preventing a 403 "Write access to repository not granted" failure that blocked the pipeline when the secret was not set.
|
||
|
||
## [0.1.1] - 2026-03-27
|
||
|
||
### Fixed
|
||
- **CI `update-k8s-manifest` job**: Fixed checkout of `k8s-cluster-state` repo by adding `ref: main` to the `actions/checkout` step, preventing a "Not Found" 404 error caused by the action's API call to determine the default branch. Also corrected the image tag format from `main-<sha>` to `sha-<sha>` to match the tags actually generated by `docker/metadata-action@v5` with `type=sha`.
|
||
- **`ProgrammingError` on `notification_configs`**: Added Alembic migration `0001` that runs `ALTER TABLE notification_configs ADD COLUMN IF NOT EXISTS` for the `name` and `apprise_url` columns introduced by the Apprise PR. SQLAlchemy's `create_all` does not ALTER existing tables, so existing deployments were missing these columns and crashing at runtime. The migration is idempotent (`IF NOT EXISTS`) so it is safe for fresh installs too. `app/main.py` lifespan now runs `alembic upgrade head` after `create_all`.
|
||
- **`/logs` page 404**: Created missing Next.js page at `src/app/logs/page.tsx`. The user-facing "Logs" sidebar link was pointing to `/logs` but no page existed. The new page lists all processing runs with expandable per-email log details and pagination.
|
||
- **`/admin/logs` page 404**: Created missing Next.js page at `src/app/admin/logs/page.tsx`. The admin "Activity Logs" sidebar link was pointing to `/admin/logs` but no page existed. The new page shows all processing runs across all users with status filtering and pagination.
|
||
- **Black formatting**: `backend/app/api/v1/endpoints/admin.py` was not formatted correctly; reformatted to pass `black --check`.
|
||
|
||
## [0.1.0] - 2026-03-26
|
||
|
||
### Added
|
||
- **Semantic Release** (`release.yml`): Automated versioning and GitHub Release creation on every push to `main` using `python-semantic-release`. Reads conventional-commit prefixes (`feat:`, `fix:`, etc.) to determine the next version and updates `CHANGELOG.md`.
|
||
- **`pyproject.toml`**: Project metadata and `[tool.semantic_release]` configuration for `python-semantic-release`.
|
||
- **GitOps auto-deployment** (step in `ci.yml`): After a successful Docker build on `main`, a new `update-k8s-manifest` job checks out `christianlouis/k8s-cluster-state` (using the `GH_PAT` secret) and updates the backend and frontend image tags in `apps/gmail-puller/preprod/gmail-puller-stack.yaml` to the new `main-<sha>` image, then commits and pushes.
|
||
- **Processing logs & reporting** — users can now view the full history of polling runs and per-email delivery status:
|
||
- **`GET /processing-runs`** — paginated list of all processing runs for the authenticated user's mailboxes (filterable by account and status).
|
||
- **`GET /processing-runs/{id}`** — details for a single run.
|
||
- **`GET /processing-runs/{id}/logs`** — per-email log entries (subject, sender, size, delivery status, error details) for a given run.
|
||
- **`GET /mail-accounts/{id}/processing-runs`** — runs scoped to a single mailbox.
|
||
- **`GET /mail-accounts/{id}/logs`** — all per-email log entries for a single mailbox.
|
||
- **Admin log endpoints** (superuser only):
|
||
- **`GET /admin/processing-runs`** — all runs across every user, filterable by user ID, account ID, or status. Account and user email addresses are GDPR-pseudonymised.
|
||
- **`GET /admin/processing-logs`** — all per-email log entries system-wide, filterable by user, account, run, or log level. Sender (`From:`) headers are pseudonymised via `mask_from_header()`; subjects are shown as-is (user-owned content).
|
||
- **`backend/app/core/gdpr.py`** — GDPR masking utilities: `mask_email()`, `mask_name()`, `mask_from_header()` for pseudonymising PII in admin views.
|
||
- **Worker now writes `ProcessingLog` entries per email** — subject, sender, size, delivery outcome and error detail are captured for every email processed by `process_mail_account`.
|
||
- **`/logs` page** — user-facing log page with a paginated processing-run table; each row expands inline to show the per-email log for that run (subject, masked sender, size, status).
|
||
- **`/admin/logs` page** — admin view with two tabs: *Processing Runs* (expandable, fetches per-email logs on demand) and *Per-Email Logs* (flat table with GDPR-masked sender addresses). Filterable by user ID and status/level.
|
||
- **Sidebar navigation** — added *Logs* link (user) and *Activity Logs* link (admin) to `DashboardLayout`.
|
||
- **Admin overview** — added *Activity Logs* card to `/admin` page.
|
||
- **Dashboard** — "Recent Processing Runs" table now reads from the new `/processing-runs` endpoint; shows account name and a *View all logs* link.
|
||
- **Apprise alerting**: New `NotificationService` using [Apprise](https://github.com/caronc/apprise) for multi-channel push notifications (Telegram, Slack, Discord, webhooks, and 80+ other services via a single URL scheme).
|
||
- `send_user_notification` — sends to all enabled per-user Apprise channels on processing errors or failures.
|
||
- `send_admin_notification` — sends to all enabled admin-wide channels for system events.
|
||
- `test_notification` — validates an Apprise URL by dispatching a test message.
|
||
- **`NotificationConfig` model**: Added `name` (friendly label) and `apprise_url` (nullable Apprise URL) columns.
|
||
- **`AdminNotificationConfig` model**: New table (`admin_notification_configs`) for system-wide admin alert channels with `name`, `apprise_url`, `is_enabled`, `notify_on_errors`, `notify_on_system_events`, and `description` fields.
|
||
- **Notifications API** (`/api/v1/notifications`): Full CRUD endpoints (GET/POST/PUT/DELETE) plus a `/test` endpoint for user notification configs.
|
||
- **Admin Notifications API** (`/api/v1/admin/notifications`): Full CRUD + `/test` endpoints for admin notification configs, superuser-only.
|
||
- **Task integration**: `process_mail_account` now calls `send_user_notification` on Gmail credential revocation, per-email forwarding failures, and unhandled processing exceptions.
|
||
- **Configurable Gmail import labels**: Users can now define which Gmail labels are applied to imported messages from the Settings page. The default setup is opinionated: `{{source_email}}` (rendered to the mailbox address each message came from) plus `imported`, and a reset button restores those defaults instantly.
|
||
- **Prometheus metrics** (`/metrics` endpoint on the FastAPI backend, scraped every 15 s):
|
||
- **HTTP layer** — `http_requests_total` (counter, labelled `method`/`endpoint`/`status_code`) and `http_request_duration_seconds` (histogram). Path segments that are numeric IDs are normalised to `{id}` to avoid label-set explosion.
|
||
- **Mail processing** — `mail_processing_runs_total` (counter, by `status`: `completed` / `partial_failure` / `failed`), `mail_processing_emails_total` (counter, by `operation`: `fetched` / `forwarded` / `failed`), `mail_processing_duration_seconds` (histogram), `active_mail_accounts_total` (gauge — set each scheduler cycle).
|
||
- **Gmail API** — `gmail_api_requests_total` (counter, by `operation` and `status`), `gmail_api_duration_seconds` (histogram, by `operation`), `gmail_token_refreshes_total` (counter), `gmail_credentials_invalidated_total` (counter).
|
||
- **Authentication / OAuth** — `auth_logins_total` (counter, `method` × `status`), `auth_registrations_total` (counter, `method` × `status`), `oauth_callbacks_total` (counter, `provider` × `status`).
|
||
- **Celery tasks** — `celery_tasks_total` (counter, `task_name` × `status`) and `celery_task_duration_seconds` (histogram, by `task_name`).
|
||
- **All metrics** defined as module-level singletons in `backend/app/core/metrics.py` (imported by HTTP middleware, task workers, GmailService, and auth endpoints).
|
||
- **Prometheus service** added to `docker-compose.new.yml` (port 9090, 30-day retention, config from `monitoring/prometheus.yml`).
|
||
- **Grafana service** added to `docker-compose.new.yml` (port 3001, auto-provisioned datasource + pre-built dashboard). Default credentials: `admin` / `admin`.
|
||
- **Pre-built Grafana dashboard** (`monitoring/grafana/dashboards/inboxconverge.json`) with five sections: Mail Processing, Gmail API, Authentication & OAuth, HTTP API, and Celery Workers. Dashboard auto-refreshes every 30 s.
|
||
- **Admin interface**: Superusers now have access to a dedicated Admin section in the sidebar with three pages:
|
||
- **Admin Overview** (`/admin`): System-wide stats (total users, mail accounts, processing runs).
|
||
- **Manage Users** (`/admin/users`): Table of all registered users with their subscription tier, status, mail account count, and last login. Admins can edit any user's name, email, plan, active status, and promote/demote admin (superuser) privileges. Users can be deleted (with confirmation).
|
||
- **Manage Plans** (`/admin/plans`): Full CRUD for subscription plans—create, edit, and delete plans with fields for tier, name, pricing, max mailboxes, max emails/day, check interval, and support level.
|
||
- **Auto-promotion of admin email**: When the user whose email matches the `ADMIN_EMAIL` environment variable logs in or registers (via email/password or Google OAuth), they are automatically promoted to superuser. Default value is `christian@inboxconverge.com` (configurable via the `ADMIN_EMAIL` env var).
|
||
- **`is_superuser` field in API responses**: `GET /users/me` and all admin user endpoints now include `is_superuser` so the frontend can conditionally show admin UI.
|
||
- **New admin API endpoints** (all require superuser role):
|
||
- `GET /admin/users` – List all users with mail account counts.
|
||
- `GET /admin/users/{id}` – Get a single user's details.
|
||
- `PUT /admin/users/{id}` – Update user details, plan, active status, and superuser flag.
|
||
- `DELETE /admin/users/{id}` – Delete a user.
|
||
- `GET /admin/plans` – List all subscription plans (including zero-price / inactive).
|
||
- `POST /admin/plans` – Create a new subscription plan.
|
||
- `PUT /admin/plans/{id}` – Update a subscription plan.
|
||
- `DELETE /admin/plans/{id}` – Delete a subscription plan.
|
||
- **Admin badge in top bar**: Admin users see a purple shield icon and an "Admin" badge next to their email in the top navigation bar.
|
||
- **`DEFAULT_USER_TIER` env var**: Controls the subscription tier assigned to every new user on registration. Defaults to `free`. Set to `enterprise` (or any other tier) for B2B / Google Workspace installations where all employees should start on a zero-rate plan.
|
||
- **`ALLOWED_DOMAINS` env var**: Comma-separated list of permitted email domains (e.g. `company.com,subsidiary.com`). When set, only addresses from those domains may register or log in. Superusers always bypass this check. Empty (default) = no restriction (normal B2C mode).
|
||
- **Dynamic pricing section on landing page**: The home page now fetches `GET /subscriptions/plans` and renders a pricing section only when paid plans exist. In enterprise / all-zero-rate deployments the pricing section is silently hidden — the page just shows features and a "Get started free" CTA.
|
||
- **B2C copy and branding**: App renamed to **InboxConverge** throughout. Landing page hero, feature cards, how-it-works, and footer rewritten in a personal, consumer-friendly tone. Pricing updated to €0.99 / €1.99 / €2.99 per month for Good / Better / Best plans.
|
||
- **Impressum & Datenschutz pages**: Added `/impressum` (legal notice per § 5 TMG) and `/datenschutz` (comprehensive privacy policy covering GDPR/DSGVO, CCPA, LGPD, and other international regulations) as public pages. Footer links to both pages were added to the dashboard layout and the login page.
|
||
- **Gmail Debug Email**: New "Send Debug Email" button in the Gmail API settings section. When clicked, it injects a test email into the user's Gmail inbox via the Gmail API. The message includes the current date in the subject line and is automatically labelled with `test` and `imported`. Useful for verifying end-to-end Gmail API delivery without requiring a full mail-account polling cycle.
|
||
- **Unified Google OAuth flow**: Google Sign-In now requests all Gmail API scopes (`gmail.insert`, `gmail.labels`, `gmail.readonly`) in the same consent screen, so users no longer need a separate "Connect Gmail" step after signing in with Google. Gmail credentials are stored automatically on successful sign-in.
|
||
- **Architecture Decision Records ADR-003 through ADR-010**: Added eight new ADRs covering FastAPI web framework (ADR-003), PostgreSQL database (ADR-004), Celery task retry strategy (ADR-005), key management in production (ADR-006), JWT authentication (ADR-007), Next.js frontend (ADR-008), Gmail API email delivery (ADR-009), and hybrid configuration model (ADR-010).
|
||
- **Account enable/disable toggle**: `PATCH /mail-accounts/{id}/toggle` backend endpoint and a Power-icon toggle button on each account card in the UI. Disabled accounts are visually dimmed. Re-enabling an account that was in ERROR state resets its status to ACTIVE so the scheduler picks it up again.
|
||
- **Message deduplication tracking** (`DownloadedMessageId` table): Both POP3 and IMAP fetch paths now track downloaded message UIDs so the same message is never delivered twice, even when `delete_after_forward=False`.
|
||
- **Gmail API "one-click" OAuth grant flow**: New `GET /providers/gmail/authorize-url` and `POST /providers/gmail/callback` endpoints with offline access and long-lived refresh tokens.
|
||
- **Gmail token auto-refresh and persistence**: `GmailService` now records whether the `google-auth` library refreshed the access token during a Celery run and persists any new access token back to `GmailCredential`, eliminating unnecessary extra refresh calls.
|
||
- **Per-user SMTP relay configuration** (`UserSmtpConfig` table): New `GET/PUT/DELETE /users/smtp-config` endpoints let each user store their own SMTP relay. The Celery task checks for per-user SMTP first; falls back to the global `AppSetting` SMTP config if none is set.
|
||
- **Settings page** — Gmail & SMTP sections: Shows a "Gmail API Delivery" card with connection status and connect/re-authorise/disconnect buttons, plus an "SMTP Fallback" card for per-user SMTP relay credentials.
|
||
- **Celery scheduling fix**: `process_all_enabled_accounts` now polls all `is_enabled = True` accounts regardless of status, so transient errors are retried automatically.
|
||
- **Backend URL logged at startup**: The Next.js server now logs the resolved `BACKEND_URL` via `src/instrumentation.ts` when the server starts, making it easy to diagnose `ECONNREFUSED` proxy errors.
|
||
- **Dual-registry Docker deployment**: CI now builds separate backend and frontend images and pushes to both GHCR (`ghcr.io`) and private registry (`registry.cklnet.com`) using a matrix strategy.
|
||
- **Database-backed configuration**: `AppSetting` model and `ConfigService` for hybrid config (DB-first, env-var fallback). Admin API endpoints for managing settings (`GET/PUT/DELETE /api/v1/settings`). Default settings seeded into database on first startup (SMTP, processing, Gmail API, notifications).
|
||
- Unit tests for `ConfigService` (24 tests covering resolution order, CRUD, SMTP helper, defaults).
|
||
- Security validation for `SECRET_KEY` and `ENCRYPTION_KEY` on startup.
|
||
- CSRF protection middleware and security headers middleware (X-Frame-Options, CSP, HSTS).
|
||
- Rate limiting per user/tier.
|
||
- Comprehensive test infrastructure setup, CI/CD pipeline, and Dependabot configuration for automated dependency updates.
|
||
|
||
### Changed
|
||
- **Project renamed to InboxConverge**: All user-visible strings, Docker container names, database defaults, Docker image paths, monitoring job names, Grafana dashboard titles, and documentation updated from the legacy names (`POP3 to Gmail Forwarder`, `InboxRescue`, `gmail-puller`, `pop3_forwarder`, etc.) to **InboxConverge** / `inboxconverge`.
|
||
- **Domain updated to `inboxconverge.com`**: All contact and administrative email addresses now default to `@inboxconverge.com` (e.g. `christian@inboxconverge.com`).
|
||
- **Configurable contact details**: Two new environment variables make contact information overridable at deployment time:
|
||
- `CONTACT_EMAIL` (default: `christian@inboxconverge.com`) — used by the frontend legal pages (Impressum, Datenschutz) and surfaced in the backend `Settings`.
|
||
- `APP_URL` (default: `https://inboxconverge.com`) — the canonical public URL of the deployment.
|
||
- `NEXT_PUBLIC_APP_NAME` (default: `InboxConverge`) — the application name shown in frontend legal-page titles; readable by Next.js server components at runtime.
|
||
- **Legacy script renamed**: `pop3_forwarder.py` → `inboxconverge.py`; root `Dockerfile` and `Makefile` updated accordingly.
|
||
- **Grafana dashboard file renamed**: `monitoring/grafana/dashboards/inboxrescue.json` → `inboxconverge.json`.
|
||
- **Note on encryption salt**: The internal PBKDF2 salt `b"pop3_forwarder_0"` in `backend/app/core/security.py` is intentionally **not** renamed — changing it would invalidate all existing encrypted credentials stored in the database.
|
||
- `NotificationConfigBase` schema: `name` field now has a default of `"My Notification"` (previously required); `apprise_url` is optional; `config` (channel-specific JSON) is optional with a default of `{}`.
|
||
- Configuration system now supports database-backed settings in addition to environment variables.
|
||
- Celery tasks (`tasks.py`) use `ConfigService` for SMTP config instead of raw `os.getenv()` calls.
|
||
- Bumped Docker Python base image from `3.11-slim` to `3.14-slim` and CI Python version from 3.11 to 3.14.
|
||
- Bumped CI Node.js version from 18 to 20.
|
||
- Bumped GitHub Actions: `actions/setup-python` v5 → v6, `actions/setup-node` v4 → v6, `docker/setup-buildx-action` v3 → v4, `codecov/codecov-action` v3 → v5.
|
||
- Bumped backend dependencies: pydantic 2.5.3 → 2.12.5, pydantic-settings 2.1.0 → 2.9.1, asyncpg 0.29.0 → 0.31.0, stripe 7.11.0 → 14.4.1, celery 5.3.6 → 5.6.2, redis 5.0.1 → 7.3.0.
|
||
- Bumped frontend dependencies: react 19.2.3 → 19.2.4, axios ^1.13.5 → ^1.13.6, eslint-config-next 16.1.6 → 16.2.1.
|
||
|
||
### Fixed
|
||
- **ESLint parse error in `DashboardLayout.tsx`**: Missing comma after `Bell` in the `lucide-react` named import caused a TypeScript parse error (`',' expected` at line 19). Added the missing comma.
|
||
- **`/processing-runs` endpoint 404s**: Routes in `logs.py` had a redundant `/processing-runs` path segment (the router was already mounted at `/processing-runs` in `api.py`). All three user-facing log endpoints now return correct results.
|
||
- **`NotificationConfigCreate` schema test failure**: `NotificationConfigBase.name` was a required field (`...`) but the unit test and the database column both use a default of `"My Notification"`. Changed the Pydantic field to `default="My Notification"` to match the DB default and allow callers to omit the field.
|
||
- Test email sender name corrected from "Christian Loris" to "Christian Krakau-Louis".
|
||
- **Mailbox limit always hit at 1**: The `subscription_plans` table was never seeded, so the limit check fell back to the env-var default. Fixed by seeding four default `SubscriptionPlan` rows at startup (Free, Good, Better, Best) and rewriting the limit check to look up the user's active plan from the DB first.
|
||
- **Zero-price plans hidden from public marketing**: `GET /subscriptions/plans` now only returns plans with `price_monthly > 0`.
|
||
- **TypeError: can't subtract offset-naive and offset-aware datetimes** in `process_mail_account` task when computing `duration_seconds`. After a database refresh, `started_at` may be returned as a naive datetime; it is now normalized to UTC before subtraction.
|
||
- **Admin user not seeing admin dashboard**: Added startup auto-promotion in `main.py` lifespan handler so users matching `ADMIN_EMAIL` are promoted to superuser on every application start.
|
||
- **Blank page on direct navigation to `/admin`, `/admin/users`, `/admin/plans`**: Moved superuser guard inside the `<AuthGuard>/<DashboardLayout>` tree so authentication always runs first.
|
||
- **Mailbox edit form**: Multiple fixes including username field pre-population, silent credential overwrite prevention, and all connection fields made editable.
|
||
- **Wizard grey screen**: `bg-opacity-75` replaced with `/75` opacity modifier syntax for Tailwind CSS v4 compatibility.
|
||
- **Mail account creation always failing**: Added missing `email_address` and `forward_to` required fields to the `AddMailAccountModal` form.
|
||
- **Settings page**: Implemented the Settings page (was a placeholder showing "coming soon").
|
||
- **`sqlalchemy.exc.DBAPIError`**: Fixed timezone-naive vs timezone-aware datetime mismatch by changing all `DateTime` columns to `DateTime(timezone=True)`.
|
||
- **`ProgrammingError` (cached statement plan is invalid)**: Disabled asyncpg prepared statement cache (`prepared_statement_cache_size=0`) to fix DDL-at-startup scenarios.
|
||
- **`UndefinedTableError` on first boot**: Lifespan startup event now calls `Base.metadata.create_all()` before attempting to seed default settings.
|
||
- **Frontend API calls hardcoded to `localhost:8000`**: Replaced `NEXT_PUBLIC_API_URL` mechanism with a Next.js Route Handler proxy at `/api/v1/[...path]` that reads `BACKEND_URL` at server startup.
|
||
- **Infinite spinning wheel on home page**: `authStore` no longer initialises `isLoading` as `true` unconditionally — it is now `false` when no access token exists in `localStorage`.
|
||
- **`useSearchParams()` Suspense boundary**: Wrapped `useSearchParams()` in a `Suspense` boundary in `frontend/src/app/auth/callback/page.tsx`.
|
||
- Various TypeScript build errors in accounts page, auth callback, and dashboard pages.
|
||
- **Node.js base image**: Upgraded from `node:18-alpine` to `node:20-alpine` to satisfy Next.js requirements.
|
||
- **Build attestation step removed**: `actions/attest-build-provenance` is not available for private user-owned repositories; removed it to fix CI.
|
||
- **ESLint downgraded**: Downgraded ESLint from `^10` to `^9` to fix `TypeError: contextOrFilename.getFilename is not a function`.
|
||
- **SQLAlchemy upgraded**: Upgraded from `2.0.25` to `2.0.48` to fix `AssertionError` on Python 3.14.
|
||
- JWT `sub` claim now encoded as string per JWT spec.
|
||
- Replaced deprecated `datetime.utcnow()` with `datetime.now(timezone.utc)` throughout backend.
|
||
- Replaced deprecated FastAPI `@app.on_event()` handlers with modern `lifespan` context manager.
|
||
- Replaced deprecated Pydantic `class Config` with `model_config = ConfigDict(...)` in all schemas.
|
||
- Open redirect vulnerability in OAuth `redirect_uri` fixed.
|
||
|
||
### Removed
|
||
- Removed CodeQL analysis from CI pipeline (was blocking builds).
|
||
|
||
### Security
|
||
- Upgraded `python-jose` from 3.3.0 to 3.5.0 to fix CVE: algorithm confusion vulnerability with OpenSSH ECDSA keys.
|
||
- Security headers added to all API responses.
|
||
- CSRF protection middleware added.
|
||
- Input validation improved for all endpoints.
|
||
- Credential handling audited and improved.
|
||
- Restricted overly permissive CORS `allow_methods` in backend.
|
||
|
||
## [1.0.0] - 2026-02-01
|
||
|
||
> **Note**: This was a pre-rewrite baseline version. The current v0.x series begins from v0.1.0 (2026-03-26).
|
||
|
||
### Added
|
||
- Multi-tenant SaaS backend with FastAPI
|
||
- JWT and OAuth2 (Google Sign-In) authentication
|
||
- Encrypted credential storage with Fernet
|
||
- Subscription management with Stripe integration
|
||
- PostgreSQL database with SQLAlchemy ORM
|
||
- Redis for caching and session management
|
||
- Celery for background task processing
|
||
- Apprise for multi-channel notifications
|
||
- Docker and docker-compose support
|
||
- Comprehensive API documentation with OpenAPI
|
||
|
||
### Changed
|
||
- Upgraded from single-user script to multi-tenant platform
|
||
|
||
## [0.0.1] - 2025-12-15
|
||
|
||
> **Note**: Legacy single-user script release (previously labelled `[0.1.0] - 2025-12-15 (Legacy Version)`).
|
||
|
||
### Added
|
||
- Initial release of single-user `inbox_converge.py` script
|
||
- Docker support with docker-compose
|
||
- Multiple POP3 account support
|
||
- Gmail forwarding via SMTP
|
||
- Rate limiting and throttling
|
||
- Error notifications via Postmarkapp
|
||
- Environment-based configuration
|
||
- Basic logging
|