15 KiB
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
# 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
# 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
# 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
# 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)
# 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
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
- Import Flow: External data → DataInbox API → Import Plugin → Import Layer
- Process Flow: Import Layer → Process Plugin → Processed Layer
- Export Flow: Processed Layer → Export Plugin → Google Sheets/Drive
- 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, GoogleDriveHelperBaseDataProcessor: Abstract base for processors, access to AppDbContext, PluginManagerBaseDataExporter: 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:
QueueJobentity: LayerId, PluginName, JobType (Import/Process), Status (Pending/Running/Completed/Failed/Retrying), Priority (0 = highest)JobSchedulerService: Creates jobs from Administration layer configsJobWorkerService: Background service polling every 5 seconds, executes jobs via PluginManager- Retry logic: 30s → 2m → 5m delays, max 5 retries
Job Lifecycle:
- Creation: JobSchedulerService or manual via
/jobs/create-for-layer/{layerId} - Queued: Status = Pending, sorted by CreatedAt DESC then Priority ASC
- Execution: JobWorkerService picks up, Status = Running
- Completion: Status = Completed or Failed
- Retry: On failure, Status = Retrying with exponential backoff
- 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:
EntityChangeInterceptorcaptures 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:
- User logs in with Google OAuth → JWT token stored in localStorage
TokenProvider.Tokenpopulated in AuthServiceMainLayoutsubscribes toAuthenticationStateChangedevent- On auth success, SignalR connection initialized with JWT token
- Token sent via
accessTokenProviderin HubConnection options
Authentication & Security
Google OAuth Flow:
- Client exchanges Google ID token →
POST /auth/apiToken - GoogleAuthService validates with Google, maps to internal User entity
- Returns JWT (7-day expiration, HS256 signing)
- JWT required on all protected endpoints except
/auth/apiToken,/health - 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, RoutesComponents/Auth/- AuthGuard, LoginCard
Code-Behind Pattern:
- Complex pages (50+ lines logic): Separate
.razor.csfiles - Simple pages: Inline
@codeblocks - 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:RedirectUriapiKey,apiUser,apiPass- DataInbox API securityexportDirectory- Google Drive folder ID for exportsInstanceName- 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
- Create new project:
DiunaBI.Plugins.{Name} - Reference: DiunaBI.Domain, DiunaBI.Infrastructure
- Inherit from: BaseDataImporter/BaseDataProcessor/BaseDataExporter
- Implement abstract methods: ImportAsync/ProcessAsync/ExportAsync, ValidateConfiguration
- Build triggers automatic copy to
bin/Plugins/via MSBuild target - No registration needed - PluginManager auto-discovers at startup
When Adding a New Entity
- Create entity in DiunaBI.Domain/Entities/
- Add DbSet to AppDbContext
- Create migration:
dotnet ef migrations add {Name} --project DiunaBI.Infrastructure --startup-project DiunaBI.API - If entity needs real-time updates, add module name to EntityChangeInterceptor
- Create DTO in DiunaBI.Application/DTOs/
- Add controller in DiunaBI.API/Controllers/ with [Authorize] and [RequireRateLimit("api")]
When Adding Real-time Updates to a UI Page
- Inject EntityChangeHubService
- Subscribe to EntityChanged event in OnInitializedAsync
- Filter by module name
- Call StateHasChanged() in event handler
- Implement IDisposable, unsubscribe in Dispose
- 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
.envfile (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)