feat(api): Add REST API controllers with RabbitMQ for POST/PUT/DELETE
Controllers:
- EmailProfilesController: CRUD operations (GET sync, POST/PUT/DELETE async via RabbitMQ)
* GET /api/emailprofiles - List all profiles
* GET /api/emailprofiles/{id} - Get profile by ID
* GET /api/emailprofiles/active - List active profiles
* POST /api/emailprofiles - Create (202 Accepted, queued to RabbitMQ)
* PUT /api/emailprofiles/{id} - Update (202 Accepted, queued to RabbitMQ)
* DELETE /api/emailprofiles/{id} - Delete (202 Accepted, queued to RabbitMQ)
- EmailAccountsController: CRUD operations
* GET /api/emailaccounts - List all accounts
* GET /api/emailaccounts/{id} - Get account by ID
* POST /api/emailaccounts - Create (202 Accepted, queued to RabbitMQ)
- EmailHistoryController: Read-only operations
* GET /api/emailhistory/profile/{profileId} - Get history with pagination
* GET /api/emailhistory/{id} - Get history by ID
Changes:
- Fix ICommandPublisher constraint: IRequest → IBaseRequest (supports IRequest<T>)
- All POST/PUT/DELETE return HTTP 202 Accepted (async processing)
- All GET operations return HTTP 200 OK (synchronous via MediatR)
- Proper error handling: 404 Not Found for missing resources
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Email accounts management API controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class EmailAccountsController(
|
||||
IMediator mediator,
|
||||
ICommandPublisher commandPublisher) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all email accounts
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<EmailAccountDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetAll(CancellationToken cancellationToken)
|
||||
{
|
||||
var query = new GetEmailAccountsQuery();
|
||||
var result = await mediator.Send(query, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get email account by ID
|
||||
/// </summary>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(EmailAccountDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create new email account (async via RabbitMQ)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> 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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Email history API controller (read-only)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class EmailHistoryController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Get email history by profile ID with pagination
|
||||
/// </summary>
|
||||
[HttpGet("profile/{profileId:int}")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EmailHistoryDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get email history by ID
|
||||
/// </summary>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(EmailHistoryDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> 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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Email profiles management API controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class EmailProfilesController(
|
||||
IMediator mediator,
|
||||
ICommandPublisher commandPublisher) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all email profiles
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<EmailProfileDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetAll(CancellationToken cancellationToken)
|
||||
{
|
||||
var query = new GetEmailProfilesQuery();
|
||||
var result = await mediator.Send(query, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get email profile by ID
|
||||
/// </summary>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(EmailProfileDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all active email profiles
|
||||
/// </summary>
|
||||
[HttpGet("active")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EmailProfileDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetActive(CancellationToken cancellationToken)
|
||||
{
|
||||
var query = new GetActiveEmailProfilesQuery();
|
||||
var result = await mediator.Send(query, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create new email profile (async via RabbitMQ)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update email profile (async via RabbitMQ)
|
||||
/// </summary>
|
||||
[HttpPut("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete email profile (async via RabbitMQ)
|
||||
/// </summary>
|
||||
[HttpDelete("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
public async Task<IActionResult> 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ public interface ICommandPublisher
|
||||
/// <summary>
|
||||
/// Publishes a command to the message broker for asynchronous processing
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">The command type (must implement IRequest)</typeparam>
|
||||
/// <typeparam name="TCommand">The command type (must implement IBaseRequest - covers both IRequest and IRequest<T>)</typeparam>
|
||||
/// <param name="command">The command to publish</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Task representing the publish operation</returns>
|
||||
Task PublishAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
||||
where TCommand : IRequest;
|
||||
where TCommand : IBaseRequest;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class RabbitMqCommandPublisher : ICommandPublisher, IDisposable
|
||||
/// Publishes a command to RabbitMQ for asynchronous processing
|
||||
/// </summary>
|
||||
public async Task PublishAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
||||
where TCommand : IRequest
|
||||
where TCommand : IBaseRequest
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(Disposed, typeof(RabbitMqCommandPublisher));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user