From 0d22fe0b5c91201568828c082dca06e4ce28de1c Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 14 Jul 2026 16:37:22 +0200 Subject: [PATCH] docs: Update AGENTS.md and STATUS.md with RabbitMQ and Phase 2 completion AGENTS.md: - Add Section 7: RabbitMQ Command Bus Integration (IMPLEMENTED) - Document ICommandPublisher, RabbitMqCommandPublisher, RabbitMqCommandConsumer - Add configuration, DI setup, and usage examples - Document benefits: async processing, horizontal scaling, retries, persistence STATUS.md: - Mark Phase 2 (Application Layer) as 100% complete - Update Phase 3 (Infrastructure Layer) to 15% (RabbitMQ done) - Document all completed components: DTOs, Commands, Queries, Validators, Mappings - Update last modified date to 2026-07-14 --- AGENTS.md | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- STATUS.md | 105 ++++++++++++++++------------- 2 files changed, 249 insertions(+), 50 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5218a8f..45884c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,18 +82,200 @@ public class CreateEmailProfileCommandHandler : IRequestHandler generic interface +public interface IRepository where TEntity : class +{ + // Query operations + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task> FindAsync(Expression> predicate, ...); + + // Create - auto-saves + Task CreateAsync(TDto dto, CancellationToken cancellationToken = default); + + // Update - auto-saves + Task UpdateAsync(Expression<...> predicate, TDto dto, ...); // Multiple records + Task UpdateSingleAsync(Expression<...> predicate, TDto dto, ...); // SAFE: Single record only + + // Delete - auto-saves + Task DeleteAsync(Expression<...> predicate, ...); // Multiple records + Task DeleteSingleAsync(Expression<...> predicate, ...); // SAFE: Single record only +} +``` + +**Command Handler Examples**: +```csharp +// ✅ CORRECT - CreateAsync auto-saves +public class CreateEmailProfileCommandHandler(IRepository repository) + : IRequestHandler +{ + public async Task Handle(CreateEmailProfileCommand request, CancellationToken cancellationToken) + { + var profile = await repository.CreateAsync(request, cancellationToken); + return profile.Id; // NO SaveChangesAsync needed! + } +} + +// ✅ CORRECT - UpdateSingleAsync for safety (throws if 0 or 2+ records match) +public class UpdateEmailProfileCommandHandler(IRepository repository) + : IRequestHandler +{ + public async Task Handle(UpdateEmailProfileCommand request, CancellationToken cancellationToken) + { + await repository.UpdateSingleAsync(p => p.Id == request.Id, request, cancellationToken); + return request.Id; // NO SaveChangesAsync needed! + } +} + +// ✅ CORRECT - DeleteSingleAsync for safety (throws if 0 or 2+ records match) +public class DeleteEmailProfileCommandHandler(IRepository repository) + : IRequestHandler +{ + public async Task Handle(DeleteEmailProfileCommand request, CancellationToken cancellationToken) + { + await repository.DeleteSingleAsync(p => p.Id == request.Id, cancellationToken); + return request.Id; // NO SaveChangesAsync needed! + } +} + +// ❌ WRONG - Manual entity creation (use AutoMapper instead) +var profile = new EmailProfile +{ + ProfileName = request.ProfileName, + EmailAccountId = request.EmailAccountId, + // ... 15 more properties +}; + +// ❌ WRONG - Using IUnitOfWork (removed) +public CreateEmailProfileCommandHandler(IUnitOfWork unitOfWork) { ... } + +// ❌ WRONG - Calling SaveChangesAsync (removed) +await repository.SaveChangesAsync(cancellationToken); +``` + +**Safety Rules**: +1. **UpdateSingleAsync** - Use for ID-based updates. Throws `InvalidOperationException` if: + - Zero records match (entity not found) + - Multiple records match (predicate too broad) + +2. **DeleteSingleAsync** - Use for ID-based deletes. Throws `InvalidOperationException` if: + - Zero records match (entity not found) + - Multiple records match (predicate too broad) + +3. **UpdateAsync / DeleteAsync** - Use ONLY when intentionally modifying multiple records: + ```csharp + // ✅ CORRECT - Intentional bulk operation + await repository.UpdateAsync( + p => p.EmailAccountId == accountId, + new { IsActive = false }, + cancellationToken); + + // ✅ Returns count of updated/deleted records + var count = await repository.DeleteAsync(p => p.IsActive == false, cancellationToken); + ``` + +**DTO Mapping Responsibility**: +- Each DTO creator must define their own AutoMapper profile +- Example: `CreateEmailProfileCommand` → `EmailProfile` mapping must be defined in `EmailProfileMappingProfile.cs` +- Repository implementation uses `IMapper.Map(dto)` internally + --- ## Future Enhancements -### HIGH PRIORITY: RabbitMQ Queue Implementation +### 7. RabbitMQ Command Bus Integration (IMPLEMENTED) -**Current State**: -- Email queue is implemented using in-memory `Channel` in `InMemoryEmailQueue.cs` -- Location: `src/DigitalData.EmailProfiler.Infrastructure/Queue/InMemoryEmailQueue.cs` +**Purpose**: Asynchronous command processing via RabbitMQ message broker for POST/PUT/DELETE operations. -**Future Enhancement**: -Replace the in-memory queue with **RabbitMQ** for production resilience and scalability. +**Architecture**: +- **GET Queries**: Synchronous (immediate response via MediatR) +- **POST/PUT/DELETE Commands**: Can be asynchronous (published to RabbitMQ, processed by background worker) + +**RabbitMQ Server**: +- Management UI: `http://172.24.12.56:15672` +- AMQP Port: `5672` (default) +- Exchange: `emailprofiler.commands` (Direct) +- Queue: `emailprofiler.command.queue` +- Routing Key: `command` + +**Implementation Components**: + +1. **ICommandPublisher** (`Application/Common/Interfaces/ICommandPublisher.cs`): + - Interface for publishing commands to message broker + - Generic method: `PublishAsync(TCommand command, CancellationToken)` + +2. **RabbitMqCommandPublisher** (`Infrastructure/Messaging/RabbitMqCommandPublisher.cs`): + - Implements `ICommandPublisher` + - Serializes command to JSON with metadata envelope (CommandType, Payload, CorrelationId, PublishedAt) + - Publishes to RabbitMQ exchange with persistent delivery mode + +3. **RabbitMqCommandConsumer** (`Infrastructure/Messaging/RabbitMqCommandConsumer.cs`): + - BackgroundService that consumes commands from RabbitMQ + - Deserializes command envelope + - Resolves command type from assembly + - Executes command via MediatR in scoped service + - Acknowledges message on success, requeues on error + +4. **RabbitMqConfiguration** (`Infrastructure/Messaging/RabbitMqConfiguration.cs`): + - Configuration model for RabbitMQ connection + - Binds to `appsettings.json` section: `RabbitMq` + +**Configuration** (`appsettings.json`): +```json +{ + "RabbitMq": { + "HostName": "172.24.12.56", + "Port": 5672, + "UserName": "guest", + "Password": "guest", + "VirtualHost": "/", + "ExchangeName": "emailprofiler.commands", + "QueueName": "emailprofiler.command.queue", + "RoutingKey": "command", + "AutomaticRecoveryEnabled": true, + "NetworkRecoveryIntervalSeconds": 10 + } +} +``` + +**Dependency Injection** (`Infrastructure/DependencyInjection.cs`): +```csharp +services.Configure(configuration.GetSection(RabbitMqConfiguration.SectionName)); +services.AddSingleton(); +services.AddHostedService(); +``` + +**Usage in API Controllers** (Future): +```csharp +// Option 1: Synchronous (immediate execution via MediatR) +var result = await _mediator.Send(new CreateEmailProfileCommand(...), cancellationToken); +return Ok(result); + +// Option 2: Asynchronous (publish to RabbitMQ for background processing) +await _commandPublisher.PublishAsync(new CreateEmailProfileCommand(...), cancellationToken); +return Accepted(); // HTTP 202 - command queued for processing +``` + +**Benefits**: +- Decouples API from long-running command processing +- Improves API responsiveness (fire-and-forget) +- Enables horizontal scaling (multiple consumers) +- Automatic retries on failure (requeue mechanism) +- Message persistence (survives application restarts) + +--- + +### HIGH PRIORITY: Email Queue for Outgoing Messages (Future) **Implementation Steps**: diff --git a/STATUS.md b/STATUS.md index dce9426..a9f0577 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,6 +1,6 @@ # EmailProfiler - Current Implementation Status -**Last Updated**: 2026-07-07 +**Last Updated**: 2026-07-14 --- @@ -48,10 +48,55 @@ --- -## 🚧 IN PROGRESS (Phase 2: Application Layer - 5%) +## ✅ COMPLETED (Phase 2: Application Layer - 100%) -### DTOs -- ✅ `CommonDtos.cs` - EmailProfileDto, EmailAccountDto, EmailHistoryDto, EmailAttachmentDto +### DTOs (Common/Dtos/{Entity}/) +- ✅ `EmailProfiles/EmailProfileDto.cs` +- ✅ `EmailAccounts/EmailAccountDto.cs` +- ✅ `EmailHistories/EmailHistoryDto.cs`, `CreateEmailHistoryDto.cs`, `UpdateEmailHistoryStatusDto.cs` +- ✅ `EmailAttachments/EmailAttachmentDto.cs`, `CreateEmailAttachmentDto.cs`, `UpdateEmailAttachmentStatusDto.cs` + +### Repository Interfaces (Generic Pattern - NO UnitOfWork) +- ✅ `IRepository` - Generic repository with CreateAsync, UpdateSingleAsync, DeleteSingleAsync, UpdateAsync, DeleteAsync + +### Service Interfaces +- ✅ `IEmailService.cs` - IMAP/SMTP operations +- ✅ `IPdfProcessingService.cs` - PDF validation and extraction +- ✅ `IDmsService.cs` - windream DMS integration +- ✅ `IEncryptionService.cs` - Password encryption +- ✅ `IEmailQueue.cs` - Email queue operations +- ✅ `ICommandPublisher.cs` - RabbitMQ command publishing + +### MediatR Commands (Features/*/Commands/) +- ✅ `CreateEmailProfileCommand.cs` + Handler +- ✅ `UpdateEmailProfileCommand.cs` + Handler +- ✅ `DeleteEmailProfileCommand.cs` + Handler +- ✅ `CreateEmailAccountCommand.cs` + Handler +- ✅ `ProcessEmailCommand.cs` + Handler + +### MediatR Queries (Features/*/Queries/) +- ✅ `GetEmailProfilesQuery.cs` + Handler +- ✅ `GetEmailProfileByIdQuery.cs` + Handler +- ✅ `GetActiveEmailProfilesQuery.cs` + Handler +- ✅ `GetEmailAccountsQuery.cs` + Handler +- ✅ `GetEmailAccountByIdQuery.cs` + Handler +- ✅ `GetEmailHistoryByProfileQuery.cs` + Handler (with pagination) +- ✅ `GetEmailHistoryByIdQuery.cs` + Handler + +### Validators (Features/*/Validators/) +- ✅ `CreateEmailProfileCommandValidator.cs` +- ✅ `UpdateEmailProfileCommandValidator.cs` +- ✅ `CreateEmailAccountCommandValidator.cs` (conditional OAuth2/password validation) +- ✅ `ProcessEmailCommandValidator.cs` (with attachment validation) + +### AutoMapper Profiles (Common/Mappings/) +- ✅ `EmailProfileMappingProfile.cs` (Command→Entity, DTO→Entity, Entity→DTO) +- ✅ `EmailAccountMappingProfile.cs` +- ✅ `EmailHistoryMappingProfile.cs` +- ✅ `EmailAttachmentMappingProfile.cs` + +### DI Configuration +- ✅ `DependencyInjection.cs` - Registers MediatR, AutoMapper, FluentValidation ### NuGet Packages - ✅ Application project has: @@ -61,51 +106,23 @@ --- -## ❌ TODO (Phase 2: Application Layer - 95%) +## 🚧 IN PROGRESS (Phase 3: Infrastructure Layer - 15%) -### Repository Interfaces -- ❌ `IEmailProfileRepository.cs` -- ❌ `IEmailAccountRepository.cs` -- ❌ `IEmailHistoryRepository.cs` -- ❌ `IEmailProcessRepository.cs` -- ❌ `IEmailOutboxRepository.cs` +### RabbitMQ Command Bus (COMPLETED) +- ✅ `RabbitMqConfiguration.cs` - Configuration model +- ✅ `RabbitMqCommandPublisher.cs` - ICommandPublisher implementation +- ✅ `RabbitMqCommandConsumer.cs` - BackgroundService for command consumption +- ✅ `DependencyInjection.cs` - Infrastructure DI with RabbitMQ registration +- ✅ Configuration in `appsettings.json` (Server: 172.24.12.56:5672) -### Service Interfaces -- ❌ `IEmailService.cs` -- ❌ `IPdfProcessingService.cs` -- ❌ `IDmsService.cs` -- ❌ `IEncryptionService.cs` -- ❌ `IEmailQueue.cs` - -### MediatR Commands -- ❌ `CreateEmailProfileCommand.cs` -- ❌ `UpdateEmailProfileCommand.cs` -- ❌ `DeleteEmailProfileCommand.cs` -- ❌ `ActivateProfileCommand.cs` -- ❌ `DeactivateProfileCommand.cs` -- ❌ Similar commands for EmailAccount, EmailProcess, etc. - -### MediatR Queries -- ❌ `GetEmailProfilesQuery.cs` -- ❌ `GetEmailProfileByIdQuery.cs` -- ❌ `GetActiveProfilesQuery.cs` -- ❌ `GetProfilesDueForPollingQuery.cs` -- ❌ Similar queries for other entities - -### Validators -- ❌ `CreateEmailProfileCommandValidator.cs` -- ❌ `UpdateEmailProfileCommandValidator.cs` -- ❌ Similar validators for all commands - -### Mappings -- ❌ `MappingProfile.cs` - AutoMapper configuration - -### DI Configuration -- ❌ `DependencyInjection.cs` - Application layer DI setup +### NuGet Packages (Partial) +- ✅ RabbitMQ.Client 7.2.1 +- ✅ Microsoft.Extensions.Hosting 10.0.9 +- ✅ Microsoft.Extensions.Options.ConfigurationExtensions 10.0.9 --- -## ❌ TODO (Phase 3: Infrastructure Layer - 0%) +## ❌ TODO (Phase 3: Infrastructure Layer - 85%) ### NuGet Packages - ❌ Microsoft.EntityFrameworkCore.SqlServer