Files
Inboxintel/docs/ARCHITECTURE.md
T
2026-06-30 15:53:32 +02:00

46 lines
5.1 KiB
Markdown

# InboxIntel Architecture
## Layering (Clean Architecture)
The solution enforces a one-directional dependency flow so the core stays testable and framework-agnostic.
`InboxIntel.Domain` holds entities (`Email`, `MailThread`, `Sender`, `MailDomain`, `Attachment`, `Label`/`EmailLabel`, `SyncState`, `AnalyticsAggregate`, `WidgetLayout`, `UnsubscribeItem`, `User`) and enums. It references nothing except Npgsql (for the `NpgsqlTsVector` full-text type).
`InboxIntel.Application` defines the contracts the rest of the system programs against: `IGmailService`, `ISyncService`, `IAnalyticsService`, `ISearchService`, `ICleanupService`, `IUnsubscribeService`, `IAiService`/`IAiProvider`, `IExportService`, plus `IAppDbContext`, DTOs, FluentValidation validators, and the `GmailQueryParser`.
`InboxIntel.Infrastructure` implements those contracts: the EF Core `AppDbContext` and entity configurations, the Gmail REST client (`GmailApiService` + `GmailClientFactory` + `GmailMessageParser`), the `SyncService` and `GmailSyncWorker`, analytics/search/cleanup/unsubscribe services, the AI providers (Null/Ollama/OpenAI) and `AiService`, the `ExportService` (PDF/CSV/JSON), and the `DataProtectionTokenProtector`.
`InboxIntel.Api` is the composition root: Serilog, Google OAuth2 + cookie auth, API versioning, CORS, controllers, and startup migration.
## Request flow
A browser calls `/api/v1/...` with the auth cookie. The controller (no business logic) resolves `ICurrentUser` to get the tenant id and calls an Application interface. The Infrastructure implementation runs EF Core queries / Gmail calls and returns DTOs. Validators run via the FluentValidation pipeline before handlers execute.
## Authentication
Google OAuth2 is the only login method. The Google handler runs with `AccessType=offline` to obtain a refresh token; `GoogleAuthEvents.OnCreatingTicketAsync` upserts the `User`, encrypts the refresh token with the Data Protection API, and stamps the internal user id (`inboxintel:uid`) as a claim. A 7-day cookie carries the session. `GmailClientFactory` decrypts the refresh token per call and lets the Google client library refresh access tokens automatically.
## Data model & scale
Every owned row carries a `UserId` (multi-user ready though single-user today). Composite indexes back the hot paths at 100k+ emails: `(UserId, GmailMessageId)` unique, `(UserId, SenderId)`, `(UserId, SentAtUtc)`, `(UserId, IsUnread)`, `(UserId, Category)`. Sender/domain rollup counters are maintained during sync for instant grouping. Full-text search uses a stored, generated `tsvector` column over subject+body with a GIN index, queried through `EF.Functions.PlainToTsQuery`. Daily `AnalyticsAggregate` rows let dashboard widgets render without scanning the email table.
## Gmail sync
Full sync pages every message id, fetches+parses each, and checkpoints the page token and counts to `SyncState` after each page — so an interrupted run resumes instead of restarting. Incremental sync replays Gmail's history feed from the stored `historyId` watermark, applying adds and deletes. All Gmail calls run through a Polly pipeline: exponential backoff with jitter on 429/5xx, capped by `GmailSync:MaxRetries`. A `BackgroundService` (`GmailSyncWorker`) runs a daily incremental sync per user and refreshes aggregates, fully off the request path. In production this can be swapped for Hangfire without touching callers.
## Cleanup & unsubscribe safety
`CleanupService.PreviewAsync` always precedes execution and returns the affected count, total size, and a sample. Destructive actions (Trash, HardDelete) are rejected unless `Confirmed == true` — enforced both by a validator and again inside the service as defence in depth. The unsubscribe pipeline detects `List-Unsubscribe` headers during sync, groups opportunities per sender, ranks them by volume, and only processes a user-confirmed queue. One-click (`List-Unsubscribe-Post`) targets are POSTed; `mailto:` targets are surfaced for the user — the app never auto-sends mail.
## AI layer
Toggleable via `Ai:Mode` (`Disabled` / `LocalOllama` / `CloudOpenAi`), selected at DI time. `IAiProvider` abstracts chat completion; `AiService` builds classification, inbox summaries, cleanup suggestions, and natural-language→Gmail-query features on top. Critically, the AI layer only reads and suggests — destructive actions always route back through the confirmed cleanup/unsubscribe flows.
## Frontend
React + Vite SPA. The dashboard uses `react-grid-layout` for draggable/resizable widgets with hide/show toggles; the layout is persisted per user via `PUT /widgetlayout`. Widgets render through Chart.js (volume line, attachment doughnut) and a custom CSS heatmap. The axios client sends the session cookie and redirects to the Google login flow on 401.
## Extension points (left intentionally open)
Gmail MIME parsing handles the common multipart/plain cases; richer HTML-body unsubscribe-URL scraping and attachment-by-attachment download are stubbed for extension. AI prompts are minimal and meant to be tuned. The background worker uses a simple hourly tick; production deployments may prefer Hangfire with a cron schedule.