Refactor solution structure and add RabbitMQ config

Reorganized the solution structure to align with a layered architecture:
- Replaced `src` folder with `core`, `infrastructure`, and `presentation`.
- Moved projects to their respective folders.
- Added `DigitalData.MessagingService.Publisher.Abstraction` project.
- Removed `DigitalData.MessagingService.Client` project.

Updated project configurations and nesting in the solution file.

Added `appsettings.Secrets.json` with RabbitMQ and email account settings:
- RabbitMQ configuration includes hostname, port, credentials, and queue/exchange details.
- Email configuration includes SMTP server details and credentials.
This commit is contained in:
2026-07-28 10:26:15 +02:00
parent 2ad2dc6b4d
commit 78c82bf129
40 changed files with 67 additions and 40 deletions

View File

@@ -0,0 +1,21 @@
namespace DigitalData.MessagingService.Application.Common.Dtos;
/// <summary>
/// DTO for EmailAccount query results.
/// </summary>
public class EmailAccountDto
{
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public bool PasswordEncrypted { get; set; } = false;
public string SmtpServer { get; set; } = null!;
public int SmtpPort { get; set; }
public bool SmtpUseSsl { get; set; }
public bool UseOAuth2 { get; set; }
}

View File

@@ -0,0 +1,19 @@
using MediatR;
namespace DigitalData.MessagingService.Application.Common.Interfaces;
/// <summary>
/// Interface for publishing commands to a message broker (e.g., RabbitMQ)
/// </summary>
public interface ICommandPublisher
{
/// <summary>
/// Publishes a command to the message broker for asynchronous processing
/// </summary>
/// <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 : IBaseRequest;
}

View File

@@ -0,0 +1,16 @@
namespace DigitalData.MessagingService.Application.Common.Interfaces;
/// <summary>
/// Email service interface for SMTP operations.
/// Implementation uses Limilabs Mail.dll for production email sending.
/// SMTP configuration is injected via IOptions&lt;EmailAccountDto&gt; in appsettings.json.
/// Throws AuthenticationFailedException when SMTP authentication fails.
/// </summary>
public interface IEmailService
{
/// <summary>
/// Sends an email using the configured SMTP account.
/// SMTP credentials are configured in appsettings.json (EmailAccount section).
/// </summary>
Task SendEmailAsync(string to, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,10 @@
namespace DigitalData.MessagingService.Application.Common.Interfaces;
/// <summary>
/// Encryption service interface for password encryption.
/// </summary>
public interface IEncryptionService
{
string Encrypt(string plainText);
string Decrypt(string cipherText);
}

View File

@@ -0,0 +1,26 @@
namespace DigitalData.MessagingService.Application.Common.Interfaces;
/// <summary>
/// PDF processing service interface.
/// Operates on streams instead of file paths for flexibility.
/// </summary>
public interface IPdfProcessingService
{
/// <summary>
/// Validates if the provided stream contains a valid PDF document.
/// Throws InvalidPdfException if the stream is not a valid PDF.
/// </summary>
Task<bool> ValidatePdfAsync(Stream pdfStream, CancellationToken cancellationToken = default);
/// <summary>
/// Extracts embedded files from PDF stream to the specified output directory.
/// Returns a list of paths to extracted files.
/// </summary>
Task<IEnumerable<string>> ExtractEmbeddedFilesAsync(Stream pdfStream, string outputDirectory, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the page count of the PDF document.
/// Throws InvalidPdfException if the stream is not a valid PDF.
/// </summary>
Task<int> GetPageCountAsync(Stream pdfStream, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,30 @@
using System.Linq.Expressions;
namespace DigitalData.MessagingService.Application.Common.Interfaces;
/// <summary>
/// Generic repository interface for CRUD operations.
/// All operations auto-save changes - NO explicit SaveChangesAsync needed!
/// </summary>
public interface IRepository<TEntity> where TEntity : class
{
// CREATE
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancellationToken = default);
// READ
Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, int? skip = null, int? take = null, CancellationToken cancellationToken = default);
Task<TEntity?> FindFirstAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<TEntity?> FindSingleAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<int> CountAsync(Expression<Func<TEntity, bool>>? predicate = null, CancellationToken cancellationToken = default);
Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
// UPDATE
Task UpdateSingleAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default);
Task<int> UpdateAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default);
// DELETE
Task DeleteSingleAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,19 @@
using AutoMapper;
using DigitalData.MessagingService.Application.EmailSending.Commands;
using DigitalData.MessagingService.Publisher.Abstraction;
namespace DigitalData.MessagingService.Application.Common.Mappings;
/// <summary>
/// AutoMapper profile for Emails
/// </summary>
public class EmailMappingProfile : Profile
{
public EmailMappingProfile()
{
// SendEmailCommand -> OutgoingEmailEvent
CreateMap<SendEmailCommand, OutgoingEmailEvent>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid()))
.ForMember(dest => dest.QueuedAt, opt => opt.MapFrom(_ => DateTime.Now));
}
}