Files
DiunaBI/CLAUDE.md
2025-12-15 20:05:26 +01:00

382 lines
15 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
DiunaBI is a full-stack Business Intelligence platform built on .NET 10.0 with a multi-tier Clean Architecture. It provides a plugin-based system for importing, processing, and exporting business data with real-time updates, job queue management, and comprehensive audit trails.
**Tech Stack:**
- Backend: ASP.NET Core 10.0 Web API
- Frontend: Blazor Server + MAUI Mobile (iOS, Android, Windows, macOS)
- Database: SQL Server + EF Core 10.0
- UI Framework: MudBlazor 8.0
- Real-time: SignalR (EntityChangeHub)
- Authentication: JWT Bearer + Google OAuth
- External APIs: Google Sheets API, Google Drive API
- Logging: Serilog (Console, File)
## Common Commands
### Build and Run
```bash
# Build the entire solution
dotnet build DiunaBI.sln
# Build specific project
dotnet build DiunaBI.API/DiunaBI.API.csproj
# Run API (backend)
dotnet run --project DiunaBI.API/DiunaBI.API.csproj
# Run Web UI (Blazor Server)
dotnet run --project DiunaBI.UI.Web/DiunaBI.UI.Web.csproj
# Clean build artifacts
dotnet clean DiunaBI.sln
```
### Database Migrations
```bash
# Add new migration
dotnet ef migrations add <MigrationName> --project DiunaBI.Infrastructure --startup-project DiunaBI.API
# Apply migrations to database
dotnet ef database update --project DiunaBI.Infrastructure --startup-project DiunaBI.API
# Remove last migration (if not applied)
dotnet ef migrations remove --project DiunaBI.Infrastructure --startup-project DiunaBI.API
# List all migrations
dotnet ef migrations list --project DiunaBI.Infrastructure --startup-project DiunaBI.API
```
### Testing
```bash
# Run all tests
dotnet test DiunaBI.Tests/DiunaBI.Tests.csproj
# Run tests with detailed output
dotnet test DiunaBI.Tests/DiunaBI.Tests.csproj --logger "console;verbosity=detailed"
```
### Plugin Development
```bash
# Build plugin project (automatically copies DLL to API bin/Plugins/)
dotnet build DiunaBI.Plugins.Morska/DiunaBI.Plugins.Morska.csproj
dotnet build DiunaBI.Plugins.PedrolloPL/DiunaBI.Plugins.PedrolloPL.csproj
# Plugins are auto-loaded from bin/Plugins/ at API startup
```
### Docker (Production)
```bash
# Build API container (with Morska plugin)
docker build -f DiunaBI.API/Dockerfile -t diunabi-api --build-arg PLUGIN_PROJECT=DiunaBI.Plugins.Morska .
# Build API container (with PedrolloPL plugin)
docker build -f DiunaBI.API/Dockerfile -t diunabi-api --build-arg PLUGIN_PROJECT=DiunaBI.Plugins.PedrolloPL .
```
## Architecture
### Solution Structure (10 Projects)
**DiunaBI.API** - ASP.NET Core Web API
- Controllers: Auth, Layers, Jobs, DataInbox
- Hubs: EntityChangeHub (SignalR)
- Services: GoogleAuthService, JwtTokenService
- API Key authorization via [ApiKeyAuth] attribute
**DiunaBI.Domain** - Domain entities (9 entities)
- User, Layer, Record, RecordHistory, QueueJob, DataInbox, ProcessSource
**DiunaBI.Application** - DTOs and application models
- LayerDto, RecordDto, UserDto, RecordHistoryDto, JobDto, PagedResult<T>
**DiunaBI.Infrastructure** - Data access and core services
- Data: AppDbContext, 47 EF Core migrations
- Interceptors: EntityChangeInterceptor (auto-broadcasts DB changes via SignalR)
- Services: PluginManager, JobSchedulerService, JobWorkerService
- Helpers: GoogleSheetsHelper, GoogleDriveHelper
- Plugin Base Classes: BaseDataImporter, BaseDataProcessor, BaseDataExporter
**DiunaBI.UI.Web** - Blazor Server web application
**DiunaBI.UI.Mobile** - MAUI mobile application (iOS, Android, Windows, macOS)
**DiunaBI.UI.Shared** - Shared Blazor component library
- Pages/: Feature-based folders (Layers/, Jobs/, DataInbox/)
- Components/: Layout/ (MainLayout, EmptyLayout, Routes), Auth/ (AuthGuard, LoginCard)
- Services/: LayerService, JobService, DataInboxService, EntityChangeHubService, AuthService
**DiunaBI.Plugins.Morska** - Production plugin (17 total)
- 4 Importers: Standard, D1, D3, FK2
- 12 Processors: D6, T1, T3, T4, T5 variants
- 1 Exporter: Google Sheets export
**DiunaBI.Plugins.PedrolloPL** - Production plugin (1 total)
- 1 Importer: B3 (DataInbox → Layer with dictionary mapping)
**DiunaBI.Tests** - Unit and integration tests
### Data Flow Architecture
1. **Import Flow**: External data → DataInbox API → Import Plugin → Import Layer
2. **Process Flow**: Import Layer → Process Plugin → Processed Layer
3. **Export Flow**: Processed Layer → Export Plugin → Google Sheets/Drive
4. **Real-time Updates**: DB Change → EntityChangeInterceptor → SignalR Hub → All Clients
### Plugin System
**Plugin Discovery:**
- Plugins loaded from `bin/Plugins/` directory at startup
- PluginManager scans assemblies for IDataImporter, IDataProcessor, IDataExporter implementations
- Plugins auto-registered in DI container
**Plugin Base Classes** (in DiunaBI.Infrastructure/Plugins/):
- `BaseDataImporter`: Abstract base for importers, access to AppDbContext, GoogleSheetsHelper, GoogleDriveHelper
- `BaseDataProcessor`: Abstract base for processors, access to AppDbContext, PluginManager
- `BaseDataExporter`: Abstract base for exporters, access to AppDbContext, GoogleSheetsHelper, GoogleDriveHelper
**Plugin Execution:**
- Importers: Read external data sources, create Layer + Records
- Processors: Read source Layers, apply transformations, create target Layers + Records
- Exporters: Read Layers, write to Google Sheets/Drive
**Plugin Configuration:**
- Stored in Administration layers (Type = ImportWorker/ProcessWorker)
- Records with Code = "Plugin", "Priority", "MaxRetries" define job configs
- JobSchedulerService reads configs and creates QueueJobs
### Job Queue System
**Components:**
- `QueueJob` entity: LayerId, PluginName, JobType (Import/Process), Status (Pending/Running/Completed/Failed/Retrying), Priority (0 = highest)
- `JobSchedulerService`: Creates jobs from Administration layer configs
- `JobWorkerService`: Background service polling every 5 seconds, executes jobs via PluginManager
- Retry logic: 30s → 2m → 5m delays, max 5 retries
**Job Lifecycle:**
1. Creation: JobSchedulerService or manual via `/jobs/create-for-layer/{layerId}`
2. Queued: Status = Pending, sorted by CreatedAt DESC then Priority ASC
3. Execution: JobWorkerService picks up, Status = Running
4. Completion: Status = Completed or Failed
5. Retry: On failure, Status = Retrying with exponential backoff
6. Real-time: All status changes broadcast via SignalR to UI
**Job Scheduling Endpoints:**
- `POST /jobs/ui/schedule` - Schedule all jobs (JWT auth for UI users)
- `POST /jobs/ui/schedule/imports` - Schedule import jobs only (JWT auth)
- `POST /jobs/ui/schedule/processes` - Schedule process jobs only (JWT auth)
- `POST /jobs/schedule/{apiKey}` - Schedule all jobs (API key auth for cron)
- `POST /jobs/schedule/imports/{apiKey}` - Schedule import jobs (API key auth)
- `POST /jobs/schedule/processes/{apiKey}` - Schedule process jobs (API key auth)
### Real-time Updates (SignalR)
**Architecture:**
- Hub: `/hubs/entitychanges` (requires JWT authentication)
- Interceptor: `EntityChangeInterceptor` captures EF Core changes (Added, Modified, Deleted)
- Broadcast: After SaveChanges, sends `EntityChanged(module, id, operation)` to all clients
- Modules: QueueJobs, Layers, Records, RecordHistory
**UI Integration:**
- `EntityChangeHubService`: Singleton service initialized after authentication in MainLayout
- Components subscribe: `HubService.EntityChanged += OnEntityChanged`
- Auto-reconnect enabled
- Pages with real-time updates: Jobs/Index, Jobs/Details, Layers/Index, Layers/Details, DataInbox/Index
**Authentication Flow:**
1. User logs in with Google OAuth → JWT token stored in localStorage
2. `TokenProvider.Token` populated in AuthService
3. `MainLayout` subscribes to `AuthenticationStateChanged` event
4. On auth success, SignalR connection initialized with JWT token
5. Token sent via `accessTokenProvider` in HubConnection options
### Authentication & Security
**Google OAuth Flow:**
1. Client exchanges Google ID token → `POST /auth/apiToken`
2. GoogleAuthService validates with Google, maps to internal User entity
3. Returns JWT (7-day expiration, HS256 signing)
4. JWT required on all protected endpoints except `/auth/apiToken`, `/health`
5. UserId extraction middleware sets X-UserId header for audit trails
**API Key Authentication:**
- Custom [ApiKeyAuth] attribute for cron job endpoints
- X-API-Key header with constant-time comparison
- Used for DataInbox external endpoints and job scheduling
**Security Features:**
- Rate limiting: 100 req/min general, 10 req/min auth
- Security headers: XSS, clickjacking, MIME sniffing protection
- Input validation: Pagination limits (1-1000), Base64 size limits (10MB)
- Stack trace hiding: Generic error messages in production
- CORS: Configured for localhost:4200, diuna.bim-it.pl, morska.diunabi.com
### Database Schema
**47 EF Core Migrations** - All in DiunaBI.Infrastructure/Migrations/
**Key Entities:**
**User**
- Id (Guid), Email, UserName
- Google OAuth identity
- Constant: `User.AutoImportUserId = "f392209e-123e-4651-a5a4-0b1d6cf9ff9d"` (system operations)
**Layer**
- Number, Name, Type (Import/Processed/Administration/Dictionary)
- ParentId (hierarchical relationships)
- IsDeleted (soft delete), IsCancelled (processing control)
- CreatedAt, ModifiedAt, CreatedById, ModifiedById (audit trail)
**Record**
- Code (unique identifier per layer), LayerId
- Value1-Value32 (double?), Desc1 (string, max 10000 chars)
- IsDeleted (soft delete)
- Full audit trail via RecordHistory
**RecordHistory** (Migration 47)
- RecordId, LayerId, ChangedAt, ChangedById
- ChangeType (Created/Updated/Deleted)
- Code, Desc1 (snapshot)
- ChangedFields (comma-separated), ChangesSummary (JSON old/new values)
- Indexes: (RecordId, ChangedAt), (LayerId, ChangedAt)
**QueueJob**
- LayerId, PluginName, JobType, Priority, Status
- RetryCount, MaxRetries (default 5)
- CreatedAt, ModifiedAt, LastAttemptAt, CompletedAt
- CreatedById, ModifiedById (uses User.AutoImportUserId for system jobs)
**DataInbox**
- Name, Source (identifiers), Data (base64-encoded JSON array)
- Used by importers to stage incoming data
**ProcessSource**
- SourceLayerId, TargetLayerId (defines layer processing relationships)
**Audit Trail Patterns:**
- All entities have CreatedAt, ModifiedAt with GETUTCDATE() defaults
- Foreign keys to Users: CreatedById, ModifiedById
- Soft deletes via IsDeleted flag
- RecordHistory tracks field-level changes with JSON diffs
### UI Organization
**Feature-Based Structure** (as of Dec 5, 2025):
- `Pages/Layers/` - Index.razor + Details.razor (list, detail, edit, history)
- `Pages/Jobs/` - Index.razor + Details.razor (list, detail, retry, cancel, scheduling)
- `Pages/DataInbox/` - Index.razor + Details.razor (list, detail, base64 decode)
- `Components/Layout/` - MainLayout, EmptyLayout, Routes
- `Components/Auth/` - AuthGuard, LoginCard
**Code-Behind Pattern:**
- Complex pages (50+ lines logic): Separate `.razor.cs` files
- Simple pages: Inline `@code` blocks
- Namespaces: `DiunaBI.UI.Shared.Pages.{Feature}`
**Filter State Persistence:**
- LayerFilterStateService, DataInboxFilterStateService
- Singleton services remember search, type, page selections across navigation
**Timezone Handling:**
- DateTimeHelper service detects browser timezone via JS Interop
- All dates stored as UTC in DB, converted to user's local timezone for display
- Format: "yyyy-MM-dd HH:mm:ss"
## Configuration
**Environment Variables** (appsettings.Development.json):
- `ConnectionStrings:SQLDatabase` - SQL Server connection (localhost:21433, DB: DiunaBI-PedrolloPL)
- `JwtSettings:SecurityKey`, `JwtSettings:ExpiryDays` (7)
- `GoogleAuth:ClientId`, `GoogleAuth:RedirectUri`
- `apiKey`, `apiUser`, `apiPass` - DataInbox API security
- `exportDirectory` - Google Drive folder ID for exports
- `InstanceName` - DEV/PROD environment identifier
**CORS Origins:**
- http://localhost:4200 (development)
- https://diuna.bim-it.pl (production)
- https://morska.diunabi.com (production)
**Logging:**
- Serilog with Console + File sinks
- Override levels: Microsoft.AspNetCore = Warning, EF Core = Warning, Google.Apis = Warning
- Sensitive data logging only in Development
## Development Patterns
### When Adding a New Plugin
1. Create new project: `DiunaBI.Plugins.{Name}`
2. Reference: DiunaBI.Domain, DiunaBI.Infrastructure
3. Inherit from: BaseDataImporter/BaseDataProcessor/BaseDataExporter
4. Implement abstract methods: ImportAsync/ProcessAsync/ExportAsync, ValidateConfiguration
5. Build triggers automatic copy to `bin/Plugins/` via MSBuild target
6. No registration needed - PluginManager auto-discovers at startup
### When Adding a New Entity
1. Create entity in DiunaBI.Domain/Entities/
2. Add DbSet to AppDbContext
3. Create migration: `dotnet ef migrations add {Name} --project DiunaBI.Infrastructure --startup-project DiunaBI.API`
4. If entity needs real-time updates, add module name to EntityChangeInterceptor
5. Create DTO in DiunaBI.Application/DTOs/
6. Add controller in DiunaBI.API/Controllers/ with [Authorize] and [RequireRateLimit("api")]
### When Adding Real-time Updates to a UI Page
1. Inject EntityChangeHubService
2. Subscribe to EntityChanged event in OnInitializedAsync
3. Filter by module name
4. Call StateHasChanged() in event handler
5. Implement IDisposable, unsubscribe in Dispose
6. Test both initial load and SignalR updates
### When Modifying Job System
- JobSchedulerService: Changes to job creation from layer configs
- JobWorkerService: Changes to job execution, retry logic, rate limiting
- QueueJob entity: Changes to job schema require migration
- Jobs UI: Real-time updates required, test SignalR broadcasts
### Security Considerations
- Never log sensitive data (tokens, passwords, API keys) except in Development
- Use generic error messages in production (no stack traces)
- All new endpoints require [Authorize] unless explicitly [AllowAnonymous]
- API key endpoints require [ApiKeyAuth] and [AllowAnonymous]
- Validate pagination parameters (1-1000 range)
- Use constant-time comparison for API keys
- Rate limit all public endpoints
## Known Limitations
- JobWorkerService polls every 5 seconds (not event-driven)
- Google Sheets API quota: 5-second delay after import jobs
- Retry delays fixed: 30s → 2m → 5m (not configurable per job)
- Plugin configuration stored in Records (not strongly typed)
- No plugin versioning or hot-reload support
- SignalR requires JWT authentication (no anonymous connections)
## Important Notes
- **DO NOT** commit `.env` file (contains secrets)
- **DO NOT** modify migration files after applying to production
- **ALWAYS** use User.AutoImportUserId for system-created entities (jobs, automated processes)
- **ALWAYS** implement IDisposable for pages subscribing to SignalR events
- **ALWAYS** test real-time updates when modifying entities with SignalR broadcasts
- **Plugin DLLs** are auto-copied to `bin/Plugins/` on build via MSBuild target
- **Database changes** require migration AND applying to production via deployment scripts
- **Foreign keys** use RESTRICT (not CASCADE) to prevent accidental data loss (Migration 45)
- **Soft deletes** via IsDeleted flag, not physical deletion
- **Timezone handling** - store UTC, display local (via DateTimeHelper)