# AGENTS.md - Development Guidelines for DoliMiddlewareApi This file contains essential information for AI coding agents working on the DoliMiddlewareApi project. Follow these guidelines to maintain consistency and quality. ## Project Overview DoliMiddlewareApi is a C# ASP.NET Core Web API (targeting .NET 10.0) that serves as a middleware between client applications and Dolibarr ERP system. It provides invoice management functionality with RESTful endpoints. ## Build/Lint/Test Commands ### Building ```bash # Build in Debug mode (default) dotnet build # Build in Release mode dotnet build -c Release # Clean and build dotnet clean && dotnet build ``` ### Running ```bash # Run the application (includes hot reload in development) dotnet run # Run with specific configuration dotnet run --environment Development ``` ### Linting/Formatting ```bash # Format code according to project standards dotnet format # Check formatting without making changes dotnet format --verify-no-changes # Format whitespace only dotnet format whitespace ``` ### Testing **Note**: This project currently has no test framework configured. When adding tests: ```bash # Add xUnit test framework (recommended) dotnet add package xunit dotnet add package Microsoft.NET.Test.Sdk dotnet add package xunit.runner.visualstudio # Run tests (after setup) dotnet test # Run tests with coverage (after adding coverlet.collector) dotnet test --collect:"XPlat Code Coverage" # Run a specific test dotnet test --filter "TestMethodName" # Run tests in a specific class dotnet test --filter "ClassName" # Run tests in watch mode dotnet watch test ``` ### Docker ```bash # Build Docker image docker build -t dolimiddlewareapi . # Run Docker container docker run -p 8080:8080 dolimiddlewareapi # Run with environment variables docker run -p 8080:8080 -e ASPNETCORE_ENVIRONMENT=Development dolimiddlewareapi ``` ## Code Style Guidelines ### Language Features & Project Setup - **Target Framework**: .NET 10.0 - **Nullable Reference Types**: Enabled - use `?` for nullable types, avoid `!` suppressions - **Implicit Usings**: Enabled - core namespaces are automatically imported - **Top-level Statements**: Not used (traditional Program.cs structure) - **Authentication**: JWT Bearer tokens with session-based caching ### Naming Conventions - **Classes**: PascalCase (e.g., `InvoiceService`, `CreateInvoiceDto`) - **Methods**: PascalCase (e.g., `GetInvoiceAsync`, `MapToInvoiceDto`) - **Properties**: PascalCase (e.g., `ClientId`, `TotalAmount`) - **Private Fields**: camelCase with underscore prefix (e.g., `_httpClient`, `_invoiceService`) - **Constants**: PascalCase (e.g., `DefaultPageSize`) - **Namespaces**: Follow folder structure (e.g., `DoliMiddlewareApi.Controllers`) - **Files**: Match class name (e.g., `InvoiceService.cs`) ### Imports & Using Statements ```csharp // Group usings by: // 1. System namespaces // 2. Microsoft namespaces // 3. Third-party packages // 4. Project namespaces using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using DoliMiddlewareApi.Dtos; using DoliMiddlewareApi.Services; // Remove unused usings automatically with: dotnet format ``` ### Formatting & Structure - **Indentation**: 4 spaces (follow .editorconfig when created) - **Braces**: K&R style (opening brace on same line) - **Line Length**: Aim for 100-120 characters, break long lines appropriately - **File Structure**: One class per file, except for small related classes ### Controller Guidelines ```csharp [ApiController] [Route("api/[controller]")] [Authorize] // JWT authentication required public class InvoicesController : ControllerBase { private readonly InvoiceService _invoiceService; // Constructor injection only public InvoicesController(InvoiceService invoiceService) { _invoiceService = invoiceService; } // Use ProducesResponseType for all endpoints [HttpGet] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task>> GetInvoices() => Ok(await _invoiceService.GetInvoicesAsync()); } ``` ### Service Layer Guidelines ```csharp public class InvoiceService { private readonly DolibarrApiClient _apiClient; public InvoiceService(DolibarrApiClient apiClient) { _apiClient = apiClient; } // Async all the way - use Task for all public methods public async Task GetInvoiceAsync(int id) { var data = await _apiClient.GetResourceAsync($"invoices/{id}"); return InvoiceMapper.MapToInvoiceDetailDto(data); } } ``` ### DTO Guidelines ```csharp // Command DTOs (input) public class CreateInvoiceDto { [Required] public int ClientId { get; set; } [Required] public DateTime Date { get; set; } [StringLength(100)] public string? Reference { get; set; } // Use meaningful defaults for optional properties public string Status { get; set; } = "draft"; [Required] [MinLength(1)] public List Lines { get; set; } = new(); } // Query DTOs (output) - handle nulls properly with nullable enabled public class InvoiceDto { public int Id { get; set; } public string Number { get; set; } = ""; // Provide defaults for non-nullable properties public DateTime? Date { get; set; } public string Status { get; set; } = ""; } ``` ### Error Handling ```csharp // Custom exceptions for business logic public class NotFoundException : Exception { public NotFoundException(string message) : base(message) { } } // Use specific exceptions in services if (invoice == null) throw new NotFoundException($"Invoice with id {id} not found"); // Global error handling in Program.cs provides consistent API responses // Logs errors appropriately based on type (expected vs unexpected) ``` ### HTTP Client Usage ```csharp // Use typed clients registered in DI builder.Services.AddHttpClient(client => { client.BaseAddress = new Uri(builder.Configuration["Dolibarr:ApiUrl"]!); client.DefaultRequestHeaders.Add("DOLAPIKEY", builder.Configuration["Dolibarr:ApiKey"]!); }); // Generic methods for common operations public async Task GetResourceAsync(string endpoint) where T : class { var response = await _httpClient.GetAsync(endpoint); await EnsureSuccessOrThrowAsync(response, endpoint); return await response.Content.ReadFromJsonAsync() ?? throw new ApiException($"Failed to deserialize response from Dolibarr for endpoint '{endpoint}'"); } ``` ### JSON Serialization ```csharp // Configure in Program.cs for consistency builder.Services.AddControllers() .AddJsonOptions(options => { // Enums as strings options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); // camelCase properties options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }); ``` ### Mapping Guidelines ```csharp // Static mapper classes public static class InvoiceMapper { public static InvoiceDto MapToInvoiceDto(InvoiceResponse response) { return new InvoiceDto { Id = int.TryParse(response.id, out int id) ? id : 0, Number = response.@ref ?? "SIN-REF", // Handle parsing failures gracefully Total = decimal.TryParse(response.total_ttc, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal total) ? Math.Round(total, 2) : null, Status = ConvertStatusToWord(response.statut) }; } // Private helper methods for complex conversions private static string ConvertStatusToWord(string? statusNumber) { return statusNumber switch { "0" => "draft", "1" => "unpaid", "2" => "paid", "3" => "cancelled", _ => "unknown" }; } } ``` ### Configuration Management ```csharp // Strongly typed configuration classes public class DolibarrSettings { public string ApiUrl { get; set; } = ""; public string ApiKey { get; set; } = ""; } // Register in Program.cs builder.Services.Configure(builder.Configuration.GetSection("Dolibarr")); ``` ### Security Best Practices - Never log sensitive data (API keys, passwords) - Use proper validation attributes on DTOs - Validate all input parameters - Use HTTPS in production - Implement proper authentication/authorization when needed ### Performance Considerations - Use async/await throughout the stack - Avoid blocking calls in async methods - Use dependency injection for proper lifetime management - Consider response caching for read operations - Use pagination for list endpoints ### Logging Guidelines ```csharp // Use structured logging with semantic parameters _logger.LogInformation("Processing invoice creation for client {ClientId}", clientId); // Log levels: // - Information: Normal operations // - Warning: Expected errors (business logic failures) // - Error: Unexpected errors (bugs) // - Debug: Detailed debugging information (development only) ``` ## Development Workflow 1. **Before making changes**: Run `dotnet build` to ensure current state compiles 2. **Make changes**: Follow the established patterns and conventions 3. **Format code**: Run `dotnet format` to ensure consistent formatting 4. **Test changes**: Run the application and test endpoints manually 5. **Build verification**: Run `dotnet build -c Release` before committing ## Architecture Patterns Used - **Clean Architecture**: Controllers → Services → External APIs - **Dependency Injection**: Constructor injection throughout - **Repository Pattern**: Not implemented (direct API calls) - **CQRS**: Separated command/query DTOs - **Mapper Pattern**: Static mappers for data transformation - **Exception Handling**: Global error handling middleware - **Service Layer Separation**: Each service has a single responsibility (SRP) - `DolibarrTokenCacheService`: Token caching (no HTTP calls) - `DolibarrApiClient`: HTTP calls only (no cache logic) - `InvoiceService`: Business logic (uses cached tokens via HTTP client) - `AuthApplicationService`: Orchestrates login + token caching - `DolibarrAuthService`: Authenticates with external API ## Future Considerations When adding tests: - Use xUnit as the testing framework - Add integration tests for API endpoints - Mock external API calls using Moq or NSubstitute - Aim for high test coverage on business logic When adding authentication: - JWT token validation with Microsoft.AspNetCore.Authentication.JwtBearer - Session-based caching for Dolibarr API tokens (handled in DolibarrTokenCacheService) - [Authorize] attributes on protected controllers When scaling: - Consider adding response caching - Implement rate limiting - Add request/response logging middleware - Consider API versioning strategy AGENTS.md