diff --git a/src/DigitalData.EmailProfiler.API/Controllers/EmailAccountsController.cs b/src/DigitalData.EmailProfiler.API/Controllers/EmailAccountsController.cs
new file mode 100644
index 0000000..d448dc6
--- /dev/null
+++ b/src/DigitalData.EmailProfiler.API/Controllers/EmailAccountsController.cs
@@ -0,0 +1,70 @@
+using DigitalData.EmailProfiler.Application.Common.Dtos;
+using DigitalData.EmailProfiler.Application.Common.Interfaces;
+using DigitalData.EmailProfiler.Application.Features.EmailAccounts.Commands;
+using DigitalData.EmailProfiler.Application.Features.EmailAccounts.Queries;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DigitalData.EmailProfiler.API.Controllers;
+
+///
+/// Email accounts management API controller
+///
+[ApiController]
+[Route("api/[controller]")]
+public class EmailAccountsController(
+ IMediator mediator,
+ ICommandPublisher commandPublisher) : ControllerBase
+{
+ ///
+ /// Get all email accounts
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetAll(CancellationToken cancellationToken)
+ {
+ var query = new GetEmailAccountsQuery();
+ var result = await mediator.Send(query, cancellationToken);
+ return Ok(result);
+ }
+
+ ///
+ /// Get email account by ID
+ ///
+ [HttpGet("{id:int}")]
+ [ProducesResponseType(typeof(EmailAccountDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetById(int id, CancellationToken cancellationToken)
+ {
+ var query = new GetEmailAccountByIdQuery(id);
+ var result = await mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ return NotFound(new { Message = $"Email account with ID {id} not found" });
+
+ return Ok(result);
+ }
+
+ ///
+ /// Create new email account (async via RabbitMQ)
+ ///
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task Create(
+ [FromBody] CreateEmailAccountCommand command,
+ CancellationToken cancellationToken)
+ {
+ // Publish command to RabbitMQ for async processing
+ await commandPublisher.PublishAsync(command, cancellationToken);
+
+ return Accepted(new
+ {
+ Message = "Email account creation request queued for processing",
+ AccountName = command.AccountName
+ });
+ }
+
+ // Note: Update and Delete operations can be added similarly
+ // For now, we focus on Create as the main use case
+}
diff --git a/src/DigitalData.EmailProfiler.API/Controllers/EmailHistoryController.cs b/src/DigitalData.EmailProfiler.API/Controllers/EmailHistoryController.cs
new file mode 100644
index 0000000..c377e24
--- /dev/null
+++ b/src/DigitalData.EmailProfiler.API/Controllers/EmailHistoryController.cs
@@ -0,0 +1,57 @@
+using DigitalData.EmailProfiler.Application.Common.Dtos.EmailHistories;
+using DigitalData.EmailProfiler.Application.Features.EmailHistories.Queries;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DigitalData.EmailProfiler.API.Controllers;
+
+///
+/// Email history API controller (read-only)
+///
+[ApiController]
+[Route("api/[controller]")]
+public class EmailHistoryController(IMediator mediator) : ControllerBase
+{
+ ///
+ /// Get email history by profile ID with pagination
+ ///
+ [HttpGet("profile/{profileId:int}")]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetByProfile(
+ int profileId,
+ [FromQuery] int pageNumber = 1,
+ [FromQuery] int pageSize = 50,
+ CancellationToken cancellationToken = default)
+ {
+ var query = new GetEmailHistoryByProfileQuery(profileId, pageNumber, pageSize);
+ var result = await mediator.Send(query, cancellationToken);
+
+ return Ok(new
+ {
+ ProfileId = profileId,
+ PageNumber = pageNumber,
+ PageSize = pageSize,
+ Data = result
+ });
+ }
+
+ ///
+ /// Get email history by ID
+ ///
+ [HttpGet("{id:int}")]
+ [ProducesResponseType(typeof(EmailHistoryDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task GetById(int id, CancellationToken cancellationToken)
+ {
+ var query = new GetEmailHistoryByIdQuery(id);
+ var result = await mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ return NotFound(new { Message = $"Email history with ID {id} not found" });
+
+ return Ok(result);
+ }
+
+ // Note: Email history is typically managed by ProcessEmailCommand
+ // No direct CREATE/UPDATE/DELETE endpoints needed
+}
diff --git a/src/DigitalData.EmailProfiler.API/Controllers/EmailProfilesController.cs b/src/DigitalData.EmailProfiler.API/Controllers/EmailProfilesController.cs
new file mode 100644
index 0000000..68772e0
--- /dev/null
+++ b/src/DigitalData.EmailProfiler.API/Controllers/EmailProfilesController.cs
@@ -0,0 +1,123 @@
+using DigitalData.EmailProfiler.Application.Common.Dtos;
+using DigitalData.EmailProfiler.Application.Common.Interfaces;
+using DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
+using DigitalData.EmailProfiler.Application.Features.EmailProfiles.Queries;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace DigitalData.EmailProfiler.API.Controllers;
+
+///
+/// Email profiles management API controller
+///
+[ApiController]
+[Route("api/[controller]")]
+public class EmailProfilesController(
+ IMediator mediator,
+ ICommandPublisher commandPublisher) : ControllerBase
+{
+ ///
+ /// Get all email profiles
+ ///
+ [HttpGet]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetAll(CancellationToken cancellationToken)
+ {
+ var query = new GetEmailProfilesQuery();
+ var result = await mediator.Send(query, cancellationToken);
+ return Ok(result);
+ }
+
+ ///
+ /// Get email profile by ID
+ ///
+ [HttpGet("{id:int}")]
+ [ProducesResponseType(typeof(EmailProfileDto), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ 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(new { Message = $"Email profile with ID {id} not found" });
+
+ return Ok(result);
+ }
+
+ ///
+ /// Get all active email profiles
+ ///
+ [HttpGet("active")]
+ [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
+ public async Task GetActive(CancellationToken cancellationToken)
+ {
+ var query = new GetActiveEmailProfilesQuery();
+ var result = await mediator.Send(query, cancellationToken);
+ return Ok(result);
+ }
+
+ ///
+ /// Create new email profile (async via RabbitMQ)
+ ///
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task Create(
+ [FromBody] CreateEmailProfileCommand command,
+ CancellationToken cancellationToken)
+ {
+ // Publish command to RabbitMQ for async processing
+ await commandPublisher.PublishAsync(command, cancellationToken);
+
+ return Accepted(new
+ {
+ Message = "Email profile creation request queued for processing",
+ ProfileName = command.ProfileName
+ });
+ }
+
+ ///
+ /// Update email profile (async via RabbitMQ)
+ ///
+ [HttpPut("{id:int}")]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task Update(
+ int id,
+ [FromBody] UpdateEmailProfileCommand command,
+ CancellationToken cancellationToken)
+ {
+ // Ensure ID matches route
+ if (id != command.Id)
+ return BadRequest(new { Message = "Route ID does not match command ID" });
+
+ // Publish command to RabbitMQ for async processing
+ await commandPublisher.PublishAsync(command, cancellationToken);
+
+ return Accepted(new
+ {
+ Message = "Email profile update request queued for processing",
+ Id = command.Id
+ });
+ }
+
+ ///
+ /// Delete email profile (async via RabbitMQ)
+ ///
+ [HttpDelete("{id:int}")]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ public async Task Delete(int id, CancellationToken cancellationToken)
+ {
+ var command = new DeleteEmailProfileCommand(id);
+
+ // Publish command to RabbitMQ for async processing
+ await commandPublisher.PublishAsync(command, cancellationToken);
+
+ return Accepted(new
+ {
+ Message = "Email profile deletion request queued for processing",
+ Id = id
+ });
+ }
+}
diff --git a/src/DigitalData.EmailProfiler.Application/Common/Interfaces/ICommandPublisher.cs b/src/DigitalData.EmailProfiler.Application/Common/Interfaces/ICommandPublisher.cs
index e63c4e3..5fb22c7 100644
--- a/src/DigitalData.EmailProfiler.Application/Common/Interfaces/ICommandPublisher.cs
+++ b/src/DigitalData.EmailProfiler.Application/Common/Interfaces/ICommandPublisher.cs
@@ -10,10 +10,10 @@ public interface ICommandPublisher
///
/// Publishes a command to the message broker for asynchronous processing
///
- /// The command type (must implement IRequest)
+ /// The command type (must implement IBaseRequest - covers both IRequest and IRequest)
/// The command to publish
/// Cancellation token
/// Task representing the publish operation
Task PublishAsync(TCommand command, CancellationToken cancellationToken = default)
- where TCommand : IRequest;
+ where TCommand : IBaseRequest;
}
diff --git a/src/DigitalData.EmailProfiler.Infrastructure/Messaging/RabbitMqCommandPublisher.cs b/src/DigitalData.EmailProfiler.Infrastructure/Messaging/RabbitMqCommandPublisher.cs
index ea179d7..11e67cc 100644
--- a/src/DigitalData.EmailProfiler.Infrastructure/Messaging/RabbitMqCommandPublisher.cs
+++ b/src/DigitalData.EmailProfiler.Infrastructure/Messaging/RabbitMqCommandPublisher.cs
@@ -84,7 +84,7 @@ public class RabbitMqCommandPublisher : ICommandPublisher, IDisposable
/// Publishes a command to RabbitMQ for asynchronous processing
///
public async Task PublishAsync(TCommand command, CancellationToken cancellationToken = default)
- where TCommand : IRequest
+ where TCommand : IBaseRequest
{
ObjectDisposedException.ThrowIf(Disposed, typeof(RabbitMqCommandPublisher));