Compare commits

...

5 Commits

Author SHA1 Message Date
OlgunR
d7c416256c Add domain exceptions and update project structure
Implemented a structured exception-handling mechanism in the
domain layer with the addition of `DomainException`,
`DomainValidationException`, `NotFoundException`, and
`PdfProcessingException` classes. These exceptions provide
specific error handling for domain logic and integrate with
centralized middleware.

Updated `ROADMAP.md` to mark Step 2.1 (Domain Exceptions) as
completed and Step 2.2 (Value Objects) as the next task.
Added timeline entries to reflect progress.

Cleaned up `DocumentOperator.Domain.csproj` by removing
unused folder inclusions, indicating a project structure
reorganization.
2026-06-16 16:38:19 +02:00
OlgunR
5d3ec27128 Enhance ROADMAP.md structure and readability
- Updated section headers with emojis for better navigation.
- Added "Last Updated" date and project status to the header.
- Corrected German umlauts and special characters for encoding.
- Improved formatting of "TABLE OF CONTENTS" and "PROJECT OVERVIEW."
- Replaced ASCII diagrams with modern box-based structures.
- Clarified dependency rules and workflow diagrams with arrows (→).
- Highlighted benefits using checkmarks () for key sections.
- Removed Ardalis.Result and marked it with  in "TECHNOLOGY STACK."
- Enhanced "DEVELOPMENT ROADMAP" with emoji-based phase statuses.
- Improved overall consistency, clarity, and visual appeal.
2026-06-16 14:54:25 +02:00
OlgunR
d5fc0c2e51 Add detailed project roadmap for DocumentOperator
Introduced a comprehensive roadmap in `ROADMAP.md` to outline
the vision, purpose, and development phases of the
`DocumentOperator` service. Key additions include:

- Table of contents for navigation.
- Project overview with problem statement, solution, and core
  features.
- Business workflow description for API operations.
- Architecture and design decisions:
  - Clean Architecture principles and dependency rules.
  - CQRS with MediatR and Vertical Slice Architecture.
  - Exception-based error handling and Minimal APIs.
- Multi-tenancy strategy using API keys.
- Technology stack and detailed project structure.
- Development roadmap with nine phases, highlighting current
  progress (Phase 1 completed, Phase 2 in progress).
- Learning notes, references, and an update log.

The roadmap serves as a living document to guide development
and ensure alignment on goals and strategies.
2026-06-16 14:26:44 +02:00
OlgunR
7272b26105 Remove Ardalis.Result package dependency
The `<PackageReference Include="Ardalis.Result" Version="10.1.0" />` was removed from the `DocumentOperator.Application.csproj` file. This indicates that the project no longer relies on the `Ardalis.Result` library. Other package references remain unchanged.
2026-06-16 13:56:56 +02:00
OlgunR
d8f3143c8a Integrate Serilog and add configuration classes
Enhanced logging with Serilog, including request logging and
structured exception handling during startup. Added support
for the Options Pattern with new configuration classes:
`DocumentOperatorSettings`, `RedisSettings`, and
`ApiKeySettings`. Introduced `TenantInfo` class for tenant
management. Updated project files to include new dependencies
and removed unused `Configuration` folder reference.
2026-06-16 11:21:03 +02:00
14 changed files with 1858 additions and 22 deletions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -10,6 +10,8 @@
<PackageReference Include="Asp.Versioning.Http" Version="8.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.28" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>

View File

@@ -1,25 +1,72 @@
using Serilog;
using DocumentOperator.Infrastructure.Configuration;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// ========================================
// 1. Serilog Configuration
// ========================================
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "DocumentOperator")
.CreateLogger();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Host.UseSerilog();
var app = builder.Build();
Log.Information("Starting DocumentOperator API...");
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
try
{
app.UseSwagger();
app.UseSwaggerUI();
// ========================================
// 2. Options Pattern Configuration
// ========================================
builder.Services.Configure<DocumentOperatorSettings>(
builder.Configuration.GetSection(DocumentOperatorSettings.SectionName));
builder.Services.Configure<RedisSettings>(
builder.Configuration.GetSection(RedisSettings.SectionName));
builder.Services.Configure<ApiKeySettings>(
builder.Configuration.GetSection(ApiKeySettings.SectionName));
// ========================================
// 3. Services
// ========================================
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ========================================
// 4. Build App
// ========================================
var app = builder.Build();
// ========================================
// 5. Middleware Pipeline
// ========================================
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseSerilogRequestLogging(); // Log HTTP Requests
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
Log.Information("DocumentOperator API started successfully");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application startup failed");
throw;
}
finally
{
Log.CloseAndFlush();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ardalis.Result" Version="10.1.0" />
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageReference Include="MediatR" Version="14.1.0" />

View File

@@ -0,0 +1,25 @@
namespace DocumentOperator.Domain.Common.Exceptions;
/// <summary>
/// Base exception for all domain-related exceptions.
/// Caught by the Exception Handling Middleware in the API layer.
/// </summary>
public abstract class DomainException : Exception
{
/// <summary>
/// Error code for categorization and logging.
/// </summary>
public string ErrorCode { get; }
protected DomainException(string message, string errorCode)
: base(message)
{
ErrorCode = errorCode;
}
protected DomainException(string message, string errorCode, Exception innerException)
: base(message, innerException)
{
ErrorCode = errorCode;
}
}

View File

@@ -0,0 +1,28 @@
namespace DocumentOperator.Domain.Common.Exceptions;
/// <summary>
/// Exception thrown when domain validation fails (e.g., invalid Value Objects).
/// Maps to HTTP 400 Bad Request in the API layer.
/// </summary>
public class DomainValidationException : DomainException
{
public string PropertyName { get; }
public DomainValidationException(string message)
: base(message, "DOMAIN_VALIDATION_ERROR")
{
PropertyName = string.Empty;
}
public DomainValidationException(string propertyName, string message)
: base(message, "DOMAIN_VALIDATION_ERROR")
{
PropertyName = propertyName;
}
public DomainValidationException(string message, Exception innerException)
: base(message, "DOMAIN_VALIDATION_ERROR", innerException)
{
PropertyName = string.Empty;
}
}

View File

@@ -0,0 +1,25 @@
namespace DocumentOperator.Domain.Common.Exceptions;
/// <summary>
/// Exception thrown when a requested resource is not found.
/// Maps to HTTP 404 Not Found in the API layer.
/// </summary>
public class NotFoundException : DomainException
{
public string ResourceType { get; }
public object ResourceId { get; }
public NotFoundException(string resourceType, object resourceId)
: base($"{resourceType} with ID '{resourceId}' was not found.", "RESOURCE_NOT_FOUND")
{
ResourceType = resourceType;
ResourceId = resourceId;
}
public NotFoundException(string resourceType, object resourceId, string customMessage)
: base(customMessage, "RESOURCE_NOT_FOUND")
{
ResourceType = resourceType;
ResourceId = resourceId;
}
}

View File

@@ -0,0 +1,34 @@
namespace DocumentOperator.Domain.Common.Exceptions;
/// <summary>
/// Exception thrown when PDF processing operations fail.
/// Maps to HTTP 500 Internal Server Error or 422 Unprocessable Entity in the API layer.
/// </summary>
public class PdfProcessingException : DomainException
{
public string Operation { get; }
public PdfProcessingException(string operation, string message)
: base($"PDF processing failed during '{operation}': {message}", "PDF_PROCESSING_ERROR")
{
Operation = operation;
}
public PdfProcessingException(string operation, string message, Exception innerException)
: base($"PDF processing failed during '{operation}': {message}", "PDF_PROCESSING_ERROR", innerException)
{
Operation = operation;
}
public PdfProcessingException(string message)
: base(message, "PDF_PROCESSING_ERROR")
{
Operation = "Unknown";
}
public PdfProcessingException(string message, Exception innerException)
: base(message, "PDF_PROCESSING_ERROR", innerException)
{
Operation = "Unknown";
}
}

View File

@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="Common\Exceptions\" />
<Folder Include="Common\Results\" />
<Folder Include="Constants\" />
<Folder Include="Models\Enums\" />

View File

@@ -0,0 +1,9 @@
namespace DocumentOperator.Infrastructure.Configuration;
public class ApiKeySettings
{
public const string SectionName = "ApiKeySettings";
public bool EnableValidation { get; set; } = true;
public Dictionary<string, TenantInfo> Keys { get; set; } = new();
}

View File

@@ -0,0 +1,11 @@
namespace DocumentOperator.Infrastructure.Configuration;
public class DocumentOperatorSettings
{
public const string SectionName = "DocumentOperatorSettings";
public string TempFolderPath { get; set; } = string.Empty;
public int TempFileRetentionHours { get; set; }
public int MaxPdfSizeMB { get; set; }
public bool EnableDetailedLogging { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace DocumentOperator.Infrastructure.Configuration;
public class RedisSettings
{
public const string SectionName = "RedisSettings";
public string ConnectionString { get; set; } = "localhost:6379";
public string InstanceName { get; set; } = "DocumentOperator:";
public int CacheExpirationMinutes { get; set; } = 60;
}

View File

@@ -0,0 +1,8 @@
namespace DocumentOperator.Infrastructure.Configuration;
public class TenantInfo
{
public string TenantId { get; set; } = string.Empty;
public string TenantName { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
}

View File

@@ -17,7 +17,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Configuration\" />
<Folder Include="DependencyInjection\" />
<Folder Include="Services\FileStorage\" />
<Folder Include="Services\DocumentValidation\" />

1640
ROADMAP.md Normal file

File diff suppressed because it is too large Load Diff