From e789afe26ab2b79e2b22cf1c416658933b192342 Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 7 Jul 2026 18:59:46 +0200 Subject: [PATCH] docs: add comprehensive implementation guide for AI agents Step-by-step guide covering all remaining phases: - Phase 2: Application Layer (Repositories, Services, Commands, Queries, Validators) - Phase 3: Infrastructure Layer (DbContext, Repositories, External Services) - Phase 4: API Layer (Controllers, Workers, Middleware) - Phase 5: Configuration (appsettings, Serilog, Scalar) - Phase 6: Testing (Unit tests, Integration tests) - Phase 7: Documentation (README.md in German) - Phase 8: Build and deployment Includes complete code examples, best practices, and verification steps. Future AI agents can follow this guide to continue development systematically. --- IMPLEMENTATION_GUIDE.md | 954 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 954 insertions(+) create mode 100644 IMPLEMENTATION_GUIDE.md diff --git a/IMPLEMENTATION_GUIDE.md b/IMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000..8c2db4a --- /dev/null +++ b/IMPLEMENTATION_GUIDE.md @@ -0,0 +1,954 @@ +# EmailProfiler - Implementation Guide for AI Agents + +## Overview + +This guide provides step-by-step instructions for AI agents to continue the implementation of the EmailProfiler application. The project is a modern .NET 8.0 rewrite of a legacy VB.NET email automation system. + +--- + +## Current Status (2026-07-07) + +βœ… **COMPLETED**: +- Domain Layer (100%) + - All entities with proper `[Table]` and `[Column]` attributes + - Value Objects (MessageId, EmailAddress) + - Enums (ErrorCode, ProcessType, etc.) + - Domain Services (MessageIdGenerator) + - Domain Events (EmailProcessedEvent) + - Exceptions (DomainException, ValidationException, AttachmentProcessingException) +- agents.md documentation +- Project builds successfully + +🚧 **IN PROGRESS**: +- Application Layer (5% - only DTOs created) + +❌ **PENDING**: +- Application Layer (95%) +- Infrastructure Layer (0%) +- API Layer (minimal structure only) +- Testing (0%) +- README.md documentation (0%) + +--- + +## Architecture Overview + +``` +DigitalData.EmailProfiler/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ Domain/ βœ… COMPLETE +β”‚ β”œβ”€β”€ Application/ 🚧 IN PROGRESS (5%) +β”‚ β”œβ”€β”€ Infrastructure/ ❌ TODO +β”‚ └── API/ ❌ TODO (minimal structure exists) +β”œβ”€β”€ tests/ +β”‚ └── Tests/ ❌ TODO +β”œβ”€β”€ legacy/ πŸ“– Reference only +β”œβ”€β”€ agents.md βœ… COMPLETE +β”œβ”€β”€ README.md ❌ TODO +└── IMPLEMENTATION_GUIDE.md πŸ“„ This file +``` + +--- + +## Phase-by-Phase Implementation Plan + +### PHASE 2: Application Layer (Current Focus) + +#### 2.1. Create Repository Interfaces + +**Location**: `src/DigitalData.EmailProfiler.Application/Interfaces/Repositories/` + +Create these files: + +**IEmailProfileRepository.cs**: +```csharp +using DigitalData.EmailProfiler.Domain.Entities; + +namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories; + +public interface IEmailProfileRepository +{ + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task> GetActiveProfilesAsync(CancellationToken cancellationToken = default); + Task> GetProfilesDueForPollingAsync(CancellationToken cancellationToken = default); + Task AddAsync(EmailProfile profile, CancellationToken cancellationToken = default); + Task UpdateAsync(EmailProfile profile, CancellationToken cancellationToken = default); + Task DeleteAsync(int id, CancellationToken cancellationToken = default); +} +``` + +**IEmailAccountRepository.cs**: +```csharp +using DigitalData.EmailProfiler.Domain.Entities; + +namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories; + +public interface IEmailAccountRepository +{ + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task> GetActiveAccountsAsync(CancellationToken cancellationToken = default); + Task AddAsync(EmailAccount account, CancellationToken cancellationToken = default); + Task UpdateAsync(EmailAccount account, CancellationToken cancellationToken = default); + Task DeleteAsync(int id, CancellationToken cancellationToken = default); +} +``` + +**IEmailHistoryRepository.cs**: +```csharp +using DigitalData.EmailProfiler.Domain.Entities; + +namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories; + +public interface IEmailHistoryRepository +{ + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task GetByMessageIdHashAsync(string hash, CancellationToken cancellationToken = default); + Task ExistsAsync(string messageIdHash, CancellationToken cancellationToken = default); + Task> GetByProfileIdAsync(int profileId, DateTime? from, DateTime? to, CancellationToken cancellationToken = default); + Task AddAsync(EmailHistory history, CancellationToken cancellationToken = default); + Task UpdateAsync(EmailHistory history, CancellationToken cancellationToken = default); +} +``` + +**IEmailProcessRepository.cs**, **IEmailOutboxRepository.cs** - Similar patterns. + +#### 2.2. Create Service Interfaces + +**Location**: `src/DigitalData.EmailProfiler.Application/Interfaces/Services/` + +**IEmailService.cs**: +```csharp +namespace DigitalData.EmailProfiler.Application.Interfaces.Services; + +public interface IEmailService +{ + Task> FetchUnreadEmailsAsync( + EmailAccount account, + CancellationToken cancellationToken = default); + + Task TestConnectionAsync( + EmailAccount account, + CancellationToken cancellationToken = default); + + Task SendEmailAsync( + EmailAccount account, + string recipient, + string subject, + string body, + bool isHtml = true, + CancellationToken cancellationToken = default); + + Task DeleteEmailAsync(EmailAccount account, int imapUid, CancellationToken cancellationToken = default); + Task MoveEmailAsync(EmailAccount account, int imapUid, string folderName, CancellationToken cancellationToken = default); +} + +public class EmailMessage +{ + public int ImapUid { get; set; } + public string MessageId { get; set; } = string.Empty; + public string From { get; set; } = string.Empty; + public string Subject { get; set; } = string.Empty; + public DateTime Date { get; set; } + public string BodyHtml { get; set; } = string.Empty; + public string BodyText { get; set; } = string.Empty; + public List Attachments { get; set; } = new(); + public byte[] RawEmailData { get; set; } = Array.Empty(); +} + +public class EmailAttachmentData +{ + public string FileName { get; set; } = string.Empty; + public string ContentType { get; set; } = string.Empty; + public byte[] Data { get; set; } = Array.Empty(); +} +``` + +**IPdfProcessingService.cs**, **IDmsService.cs**, **IEncryptionService.cs**, **IEmailQueue.cs** - See agents.md for examples. + +#### 2.3. Create MediatR Commands + +**Location**: `src/DigitalData.EmailProfiler.Application/EmailProfiles/Commands/` + +**CreateEmailProfileCommand.cs**: +```csharp +using MediatR; + +namespace DigitalData.EmailProfiler.Application.EmailProfiles.Commands; + +public record CreateEmailProfileCommand( + string ProfileName, + int EmailAccountId, + int? ProcessId, + int PollIntervalMinutes) : IRequest; + +public class CreateEmailProfileCommandHandler : IRequestHandler +{ + private readonly IEmailProfileRepository _repository; + + public CreateEmailProfileCommandHandler(IEmailProfileRepository repository) + { + _repository = repository; + } + + public async Task Handle(CreateEmailProfileCommand request, CancellationToken cancellationToken) + { + var profile = new EmailProfile + { + ProfileName = request.ProfileName, + EmailAccountId = request.EmailAccountId, + ProcessId = request.ProcessId, + PollIntervalMinutes = request.PollIntervalMinutes, + IsActive = true, + AddedWhen = DateTime.UtcNow + }; + + return await _repository.AddAsync(profile, cancellationToken); + } +} +``` + +**UpdateEmailProfileCommand.cs**, **DeleteEmailProfileCommand.cs**, **ActivateProfileCommand.cs** - Similar patterns. + +#### 2.4. Create MediatR Queries + +**Location**: `src/DigitalData.EmailProfiler.Application/EmailProfiles/Queries/` + +**GetEmailProfilesQuery.cs**: +```csharp +using MediatR; +using AutoMapper; +using DigitalData.EmailProfiler.Application.Common.Dtos; + +namespace DigitalData.EmailProfiler.Application.EmailProfiles.Queries; + +public record GetEmailProfilesQuery : IRequest>; + +public class GetEmailProfilesQueryHandler : IRequestHandler> +{ + private readonly IEmailProfileRepository _repository; + private readonly IMapper _mapper; + + public GetEmailProfilesQueryHandler(IEmailProfileRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + + public async Task> Handle(GetEmailProfilesQuery request, CancellationToken cancellationToken) + { + var profiles = await _repository.GetAllAsync(cancellationToken); + return _mapper.Map>(profiles); + } +} +``` + +**GetEmailProfileByIdQuery.cs**, **GetActiveProfilesQuery.cs**, **GetProfilesDueForPollingQuery.cs** - Similar patterns. + +#### 2.5. Create Validators + +**Location**: `src/DigitalData.EmailProfiler.Application/EmailProfiles/Validators/` + +**CreateEmailProfileCommandValidator.cs**: +```csharp +using FluentValidation; +using DigitalData.EmailProfiler.Application.EmailProfiles.Commands; + +namespace DigitalData.EmailProfiler.Application.EmailProfiles.Validators; + +public class CreateEmailProfileCommandValidator : AbstractValidator +{ + public CreateEmailProfileCommandValidator() + { + RuleFor(x => x.ProfileName) + .NotEmpty().WithMessage("Profile name is required") + .MaximumLength(100).WithMessage("Profile name must not exceed 100 characters"); + + RuleFor(x => x.EmailAccountId) + .GreaterThan(0).WithMessage("Email account ID must be greater than 0"); + + RuleFor(x => x.PollIntervalMinutes) + .GreaterThan(0).WithMessage("Poll interval must be greater than 0") + .LessThanOrEqualTo(1440).WithMessage("Poll interval must not exceed 1440 minutes (24 hours)"); + } +} +``` + +#### 2.6. Create AutoMapper Profiles + +**Location**: `src/DigitalData.EmailProfiler.Application/Common/Mappings/` + +**MappingProfile.cs**: +```csharp +using AutoMapper; +using DigitalData.EmailProfiler.Domain.Entities; +using DigitalData.EmailProfiler.Application.Common.Dtos; + +namespace DigitalData.EmailProfiler.Application.Common.Mappings; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + // EmailProfile mappings + CreateMap() + .ForMember(d => d.EmailAccountName, opt => opt.MapFrom(s => s.EmailAccount != null ? s.EmailAccount.AccountName : null)) + .ForMember(d => d.ProcessName, opt => opt.MapFrom(s => s.EmailProcess != null ? s.EmailProcess.ProcessName : null)); + + // EmailAccount mappings + CreateMap(); + + // EmailHistory mappings + CreateMap() + .ForMember(d => d.ProfileName, opt => opt.MapFrom(s => s.Profile != null ? s.Profile.ProfileName : null)) + .ForMember(d => d.Attachments, opt => opt.MapFrom(s => s.Attachments)); + + // EmailAttachment mappings + CreateMap(); + } +} +``` + +#### 2.7. Create DependencyInjection.cs + +**Location**: `src/DigitalData.EmailProfiler.Application/DependencyInjection.cs` + +```csharp +using Microsoft.Extensions.DependencyInjection; +using FluentValidation; +using System.Reflection; + +namespace DigitalData.EmailProfiler.Application; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + + // MediatR + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly)); + + // AutoMapper + services.AddAutoMapper(assembly); + + // FluentValidation + services.AddValidatorsFromAssembly(assembly); + + return services; + } +} +``` + +--- + +### PHASE 3: Infrastructure Layer + +#### 3.1. Add NuGet Packages + +```bash +cd src/DigitalData.EmailProfiler.Infrastructure +dotnet add package Microsoft.EntityFrameworkCore.SqlServer +dotnet add package Microsoft.EntityFrameworkCore.Tools +dotnet add package MailKit +dotnet add package MimeKit +dotnet add package PdfSharp +dotnet add package Microsoft.Identity.Client +dotnet add package Microsoft.AspNetCore.DataProtection +``` + +#### 3.2. Create DbContext + +**Location**: `src/DigitalData.EmailProfiler.Infrastructure/Persistence/EmailProfilerDbContext.cs` + +```csharp +using Microsoft.EntityFrameworkCore; +using DigitalData.EmailProfiler.Domain.Entities; +using System.Reflection; + +namespace DigitalData.EmailProfiler.Infrastructure.Persistence; + +public class EmailProfilerDbContext : DbContext +{ + public EmailProfilerDbContext(DbContextOptions options) : base(options) { } + + public DbSet EmailAccounts { get; set; } + public DbSet EmailProfiles { get; set; } + public DbSet EmailProcesses { get; set; } + public DbSet ProcessSteps { get; set; } + public DbSet IndexingSteps { get; set; } + public DbSet EmailHistories { get; set; } + public DbSet EmailAttachments { get; set; } + public DbSet EmailOutbox { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Apply configurations from assembly (if you create IEntityTypeConfiguration classes) + // modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + + // Note: All entity configurations are already done via attributes in Domain entities + // This is important - DO NOT modify database schema here! + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + // Auto-populate audit fields + var entries = ChangeTracker.Entries(); + + foreach (var entry in entries) + { + if (entry.State == EntityState.Added) + { + entry.Entity.CreatedDate = DateTime.UtcNow; + entry.Entity.CreatedBy = "System"; // TODO: Get from current user context + } + + if (entry.State == EntityState.Modified) + { + entry.Entity.ModifiedDate = DateTime.UtcNow; + entry.Entity.ModifiedBy = "System"; // TODO: Get from current user context + } + } + + return base.SaveChangesAsync(cancellationToken); + } +} +``` + +#### 3.3. Create Repositories + +**Location**: `src/DigitalData.EmailProfiler.Infrastructure/Persistence/Repositories/` + +**EmailProfileRepository.cs**: +```csharp +using Microsoft.EntityFrameworkCore; +using DigitalData.EmailProfiler.Domain.Entities; +using DigitalData.EmailProfiler.Application.Interfaces.Repositories; + +namespace DigitalData.EmailProfiler.Infrastructure.Persistence.Repositories; + +public class EmailProfileRepository : IEmailProfileRepository +{ + private readonly EmailProfilerDbContext _context; + + public EmailProfileRepository(EmailProfilerDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + return await _context.EmailProfiles + .Include(p => p.EmailAccount) + .Include(p => p.EmailProcess) + .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _context.EmailProfiles + .Include(p => p.EmailAccount) + .Include(p => p.EmailProcess) + .OrderBy(p => p.Sequence) + .ToListAsync(cancellationToken); + } + + public async Task> GetActiveProfilesAsync(CancellationToken cancellationToken = default) + { + return await _context.EmailProfiles + .Include(p => p.EmailAccount) + .Include(p => p.EmailProcess) + .Where(p => p.IsActive && p.EmailAccount!.IsActive) + .OrderBy(p => p.Sequence) + .ToListAsync(cancellationToken); + } + + public async Task> GetProfilesDueForPollingAsync(CancellationToken cancellationToken = default) + { + var now = DateTime.UtcNow; + + return await _context.EmailProfiles + .Include(p => p.EmailAccount) + .Include(p => p.EmailProcess) + .Where(p => p.IsActive + && p.EmailAccount!.IsActive + && (!p.LastPollTime.HasValue || + EF.Functions.DateDiffMinute(p.LastPollTime.Value, now) >= p.PollIntervalMinutes)) + .OrderBy(p => p.Sequence) + .ToListAsync(cancellationToken); + } + + public async Task AddAsync(EmailProfile profile, CancellationToken cancellationToken = default) + { + _context.EmailProfiles.Add(profile); + await _context.SaveChangesAsync(cancellationToken); + return profile.Id; + } + + public async Task UpdateAsync(EmailProfile profile, CancellationToken cancellationToken = default) + { + _context.EmailProfiles.Update(profile); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) + { + var profile = await GetByIdAsync(id, cancellationToken); + if (profile != null) + { + _context.EmailProfiles.Remove(profile); + await _context.SaveChangesAsync(cancellationToken); + } + } +} +``` + +Create similar repositories for **EmailAccountRepository**, **EmailHistoryRepository**, etc. + +#### 3.4. Create External Services + +**MailKitEmailService.cs**, **PdfSharpProcessingService.cs**, **WindreamDmsService.cs**, **EncryptionService.cs**, **InMemoryEmailQueue.cs** + +(See agents.md for examples - these are complex services) + +#### 3.5. Create DependencyInjection.cs + +**Location**: `src/DigitalData.EmailProfiler.Infrastructure/DependencyInjection.cs` + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.EntityFrameworkCore; +using DigitalData.EmailProfiler.Infrastructure.Persistence; +using DigitalData.EmailProfiler.Application.Interfaces.Repositories; +using DigitalData.EmailProfiler.Infrastructure.Persistence.Repositories; + +namespace DigitalData.EmailProfiler.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + // DbContext + services.AddDbContext(options => + options.UseSqlServer( + configuration.GetConnectionString("DefaultConnection"), + sqlOptions => + { + sqlOptions.EnableRetryOnFailure( + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(30), + errorNumbersToAdd: null); + sqlOptions.CommandTimeout(60); + })); + + // Repositories + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + // ... add other repositories + + // External Services + // services.AddScoped(); + // services.AddScoped(); + // services.AddScoped(); + // services.AddScoped(); + // services.AddSingleton(); + + return services; + } +} +``` + +--- + +### PHASE 4: API Layer + +#### 4.1. Add NuGet Packages + +```bash +cd src/DigitalData.EmailProfiler.API +dotnet add package Serilog.AspNetCore +dotnet add package Serilog.Sinks.File +dotnet add package Serilog.Sinks.MSSqlServer +dotnet add package Scalar.AspNetCore +``` + +#### 4.2. Update Program.cs + +**Location**: `src/DigitalData.EmailProfiler.API/Program.cs` + +```csharp +using DigitalData.EmailProfiler.API; +using DigitalData.EmailProfiler.Application; +using DigitalData.EmailProfiler.Infrastructure; +using Serilog; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +// Configure Serilog +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/emailprofiler-.log", rollingInterval: RollingInterval.Day) + .CreateLogger(); + +builder.Host.UseSerilog(); + +// Check for Windows Service mode +if (builder.Configuration["Hosting:Mode"] == "WindowsService") +{ + builder.Host.UseWindowsService(); +} + +// Add services +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Add Application and Infrastructure layers +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); + +// Add Background Workers +// builder.Services.AddHostedService(); +// builder.Services.AddHostedService(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); + + // Add Scalar + app.MapScalarApiReference(); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +try +{ + Log.Information("Starting EmailProfiler API"); + app.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Application start-up failed"); +} +finally +{ + Log.CloseAndFlush(); +} +``` + +#### 4.3. Create Controllers + +**Location**: `src/DigitalData.EmailProfiler.API/Controllers/` + +**EmailProfilesController.cs**: +```csharp +using Microsoft.AspNetCore.Mvc; +using MediatR; +using DigitalData.EmailProfiler.Application.EmailProfiles.Commands; +using DigitalData.EmailProfiler.Application.EmailProfiles.Queries; + +namespace DigitalData.EmailProfiler.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class EmailProfilesController : ControllerBase +{ + private readonly IMediator _mediator; + private readonly ILogger _logger; + + public EmailProfilesController(IMediator mediator, ILogger logger) + { + _mediator = mediator; + _logger = logger; + } + + [HttpGet] + public async Task GetAll(CancellationToken cancellationToken) + { + var query = new GetEmailProfilesQuery(); + var result = await _mediator.Send(query, cancellationToken); + return Ok(result); + } + + [HttpGet("{id}")] + public async Task GetById(int id, CancellationToken cancellationToken) + { + var query = new GetEmailProfileByIdQuery(id); + var result = await _mediator.Send(query, cancellationToken); + + if (result == null) + return NotFound(); + + return Ok(result); + } + + [HttpPost] + public async Task Create(CreateEmailProfileCommand command, CancellationToken cancellationToken) + { + var id = await _mediator.Send(command, cancellationToken); + return CreatedAtAction(nameof(GetById), new { id }, id); + } + + // Add Update, Delete, Activate, Deactivate endpoints +} +``` + +Create similar controllers for **EmailAccountsController**, **EmailHistoryController**, **DashboardController**. + +#### 4.4. Create Background Workers + +**Location**: `src/DigitalData.EmailProfiler.API/Workers/` + +**EmailPollingWorker.cs** and **EmailSenderWorker.cs** (See agents.md for implementation examples) + +#### 4.5. Update appsettings.json + +**Location**: `src/DigitalData.EmailProfiler.API/appsettings.json` + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(local);Database=DD_ECM;Integrated Security=true;TrustServerCertificate=true" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + } + }, + "Workers": { + "EmailPolling": { + "Enabled": true, + "IntervalSeconds": 60 + }, + "EmailSender": { + "Enabled": true, + "IntervalSeconds": 5 + } + }, + "Hosting": { + "Mode": "IIS" + } +} +``` + +--- + +### PHASE 5: Testing + +#### 5.1. Add NuGet Packages + +```bash +cd tests/DigitalData.EmailProfiler.Tests +dotnet add package FakeItEasy +dotnet add package Bogus +dotnet add package FluentAssertions +dotnet add package Microsoft.AspNetCore.Mvc.Testing +dotnet add package Testcontainers.MsSql +``` + +#### 5.2. Create Unit Tests + +**Location**: `tests/DigitalData.EmailProfiler.Tests/Unit/Domain/` + +**MessageIdGeneratorTests.cs**: +```csharp +using Xunit; +using FluentAssertions; +using DigitalData.EmailProfiler.Domain.Services; + +namespace DigitalData.EmailProfiler.Tests.Unit.Domain; + +public class MessageIdGeneratorTests +{ + [Fact] + public void Generate_ShouldCreateValidMessageId() + { + // Arrange + var generator = new MessageIdGenerator(); + var original = "test-msg-123"; + var sender = "sender@example.com"; + var date = new DateTime(2026, 1, 1, 12, 0, 0); + var subject = "Test Subject"; + + // Act + var messageId = generator.Generate(original, sender, date, subject); + + // Assert + messageId.Should().NotBeNull(); + messageId.Hash.Should().NotBeNullOrEmpty(); + messageId.Value.Should().Contain(original); + messageId.Value.Should().Contain(sender); + } + + [Fact] + public void Generate_SameInput_ShouldProduceSameHash() + { + // Arrange + var generator = new MessageIdGenerator(); + var original = "test-msg-123"; + var sender = "sender@example.com"; + var date = new DateTime(2026, 1, 1, 12, 0, 0); + var subject = "Test Subject"; + + // Act + var messageId1 = generator.Generate(original, sender, date, subject); + var messageId2 = generator.Generate(original, sender, date, subject); + + // Assert + messageId1.Hash.Should().Be(messageId2.Hash); + } +} +``` + +#### 5.3. Create Integration Tests + +Use Testcontainers for database integration tests. + +--- + +### PHASE 6: Documentation + +#### 6.1. Create README.md (in German) + +**Location**: `README.md` + +The README should include (in German): +- Application overview +- Architecture diagram +- API endpoints documentation +- Worker processes description +- Database tables documentation +- Configuration guide (appsettings.json) +- Deployment instructions (IIS and Windows Service) +- Troubleshooting guide + +**Template structure**: +```markdown +# DigitalData EmailProfiler + +## Übersicht +[Application overview in German] + +## Architektur +[Architecture description] + +## API Endpunkte + +### Email Profile Management +- GET /api/emailprofiles - Alle Profile abrufen +- GET /api/emailprofiles/{id} - Profil nach ID abrufen +- POST /api/emailprofiles - Neues Profil erstellen +- PUT /api/emailprofiles/{id} - Profil aktualisieren +- DELETE /api/emailprofiles/{id} - Profil lΓΆschen + +[... continue for all controllers] + +## Background Workers + +### EmailPollingWorker +Überwacht E-Mail-Konten und verarbeitet eingehende E-Mails. + +**Konfiguration**: +```json +"Workers": { + "EmailPolling": { + "Enabled": true, + "IntervalSeconds": 60 + } +} +``` + +[... continue for all workers] + +## Datenbank Tabellen + +### TBDD_EMAIL_ACCOUNT +[Table description] + +[... continue for all tables] + +## Konfiguration + +[Detailed configuration guide] + +## Deployment + +### IIS Deployment +[Step-by-step guide] + +### Windows Service Deployment +[Step-by-step guide] +``` + +--- + +## Build and Test Commands + +```bash +# Build solution +dotnet build + +# Run tests +dotnet test + +# Run API +cd src/DigitalData.EmailProfiler.API +dotnet run + +# Create migration +cd src/DigitalData.EmailProfiler.Infrastructure +dotnet ef migrations add InitialCreate --startup-project ../DigitalData.EmailProfiler.API + +# Update database +dotnet ef database update --startup-project ../DigitalData.EmailProfiler.API +``` + +--- + +## Important Reminders for AI Agents + +1. **NEVER modify database schema** - use `[Table]` and `[Column]` attributes +2. **NEVER commit to git** - wait for user instruction +3. **All code and comments in English** - except README.md (German) +4. **Use Serilog for logging** - structured logging +5. **Worker intervals configurable** - via appsettings.json +6. **Support IIS and Windows Service** - via configuration +7. **Check for database triggers** - add to DbContext if they exist +8. **RabbitMQ is future enhancement** - currently use InMemoryEmailQueue + +--- + +## Next Steps for Continuation + +1. Complete Application Layer (Commands, Queries, Validators) +2. Complete Infrastructure Layer (DbContext, Repositories, Services) +3. Complete API Layer (Controllers, Workers, Middleware) +4. Create comprehensive tests +5. Write README.md in German +6. Build and test the complete application + +--- + +**Document Version**: 1.0 +**Last Updated**: 2026-07-07 +**Status**: Phase 1 Complete, Phase 2-6 Pending