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));