Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/content/docs/changelog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Notable changes to the kit, newest first.

## 2026-09-30

- **Files: the purge jobs now clean every tenant and refund storage quota (fix).** `PurgeOrphanedFilesJob` and `PurgeDeletedFilesJob` ran without a tenant, so they only reached the shared database: tenants with a dedicated database kept expired uploads and soft-deleted files (rows and blobs) forever. Both jobs now run per tenant inside that tenant's context, lifting only the soft-delete filter. Purging soft-deleted files also refunds the freed bytes to the owning tenant's storage quota, a refund that was silently lost before. See [#1419](https://github.com/fullstackhero/dotnet-starter-kit/pull/1419).
- **Dashboard: Live activity keeps its history across a refresh.** Users with `Permissions.AuditTrails.View` now see a **Recent history** section on `/activity`: the latest 25 audited events for the tenant, without per-request system activity, loaded once per visit with a manual refresh. Live SSE events stay in memory in their own section and are never written to browser storage. Users without the permission get a live-only view that explains where past activity lives, and the overview's **Recent audits** card no longer calls `/audits` for them (it showed a `403` before). See [#1420](https://github.com/fullstackhero/dotnet-starter-kit/pull/1420).
- **Auditing: the retention job now actually purges audit records, in every tenant (fix).** The `auditing-retention` Hangfire job is registered without a tenant, and `AuditRecords` carries the default-on tenant filter, so every run threw a `NullReferenceException` inside the filter and deleted nothing: with `Auditing:Retention:Enabled` on, audit tables still grew without limit. The job now loads every tenant from the tenant store and runs the sweep inside each tenant's context, which also reaches tenants with a dedicated database. A failure in one tenant is logged with its `TenantId` and the other tenants still run. Retention options and batching are unchanged. See [#1413](https://github.com/fullstackhero/dotnet-starter-kit/pull/1413).
- **Production mail settings now bind (fix).** `appsettings.Production.json` put `Host`, `Port`, `UserName` and `Password` directly under `MailOptions` instead of `MailOptions:Smtp`, so they bound to nothing and Production silently inherited the base `smtp.ethereal.email` host. They now sit under `MailOptions:Smtp`, with the port defaulting to `587`. Set `MailOptions__Smtp__Host` (and credentials) for Production; with the blank default, sending fails at send time. See [#1414](https://github.com/fullstackhero/dotnet-starter-kit/pull/1414).
- **Docker: the API and DbMigrator images build on supported base images.** Both Dockerfiles moved from `mcr.microsoft.com/dotnet/nightly/aspnet:10.0-noble-chiseled` to the supported `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`. The SDK container publish (`ContainerFamily`, used by the AWS deploy) stays on full `noble` on purpose. See [#1414](https://github.com/fullstackhero/dotnet-starter-kit/pull/1414).
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/frontend/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ The context handles:
- Automatic reconnect with backoff + jitter (capped at 60 s), with status (`idle / connecting / connected / reconnecting / error`) surfaced as a state pill.
- Transport negotiation (WebSockets, then SSE, then long-polling) for proxy-hostile networks.

**SSE** - `clients/dashboard/src/sse/sse-context.tsx` streams the live activity feed. The client first exchanges its JWT for a short-lived stream token (`POST /api/v1/sse/token`), then opens `GET /api/v1/sse/stream?token=...` and parses the `text/event-stream` itself (fetch + ReadableStream, with reconnect/backoff). Overview and the Activity page render the same event buffer.
**SSE** - `clients/dashboard/src/sse/sse-context.tsx` streams the live activity feed. The client first exchanges its JWT for a short-lived stream token (`POST /api/v1/sse/token`), then opens `GET /api/v1/sse/stream?token=...` and parses the `text/event-stream` itself (fetch + ReadableStream, with reconnect/backoff). Overview and the Activity page render the same event buffer. The buffer lives in memory only and is empty after a refresh; the Activity page adds a **Recent history** section (latest 25 audits, loaded once per visit) for users with `Permissions.AuditTrails.View`, and the Overview's Recent audits card is gated on the same permission.

<Screenshot src="/screenshots/dashboard/realtime-status-pill.png"
alt="Realtime status pill in the sidebar"
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/modules/files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ If your module owns files, ship a `FileAccessPolicy : IFileAccessPolicy` next to
- **Per-category validation** - extension whitelist + size cap per category (`Image` 10 MB, `Document` 25 MB, `Archive` 50 MB), configured in `appsettings`.
- **Visibility** - files are `Public` or `Private`; `PATCH /files/{id}/visibility` flips it after upload (policy-gated, only on `Available` files). `GET /files/shared` lists the tenant's public free-standing files (`MyFiles` / `User` owner types) for a "Shared in tenant" surface.
- **File scanner hook** - `IFileScanner.ScanAsync(storageKey)` with `NoOpFileScanner` as the default (always `Clean`). Implement it to plug in ClamAV / GuardDuty / VirusTotal; an `Infected` scan result transitions the file to `Quarantined` instead of `Available`.
- **Soft delete + retention** - deleted files go to trash; `PurgeDeletedFilesJob` (Hangfire, daily 03:30 UTC) hard-deletes after 30 days. The tenant dashboard's Trash page restores from here (permission-gated tab).
- **Soft delete + retention** - deleted files go to trash; `PurgeDeletedFilesJob` (Hangfire, daily 03:30 UTC) hard-deletes after 30 days. The tenant dashboard's Trash page restores from here (permission-gated tab). Both purge jobs run once per tenant (including tenants with a dedicated database), and the soft-delete purge refunds the freed bytes to that tenant's storage quota.
- **Orphan cleanup** - `PurgeOrphanedFilesJob` (hourly) deletes `PendingUpload` rows whose upload deadline passed without a finalize call.
- **Storage quota metering** - finalize records the uploaded bytes against the tenant's `StorageBytes` quota.
- **`FileFinalizedIntegrationEvent`** - published when a file is finalized (`Available` or `Quarantined`), carrying owner type/id, content type, size, and final status. No built-in consumer ships today - Catalog and Chat attach files via explicit commands carrying the `fileAssetId` + URL - but it's the hook for search indexing, notifications, and the like.
Expand Down