refactor(application): Remove IUnitOfWork, add generic IRepository<T> with AutoMapper

- Remove IUnitOfWork pattern (not needed with auto-save repositories)
- Add generic IRepository<T> with CreateAsync<TDto>, UpdateSingleAsync<TDto>, DeleteSingleAsync
- Add UpdateAsync/DeleteAsync for bulk operations
- Add ICommandPublisher interface for RabbitMQ integration
- Add service interfaces: IEmailService, IPdfProcessingService, IDmsService, IEncryptionService, IEmailQueue
- Configure Application DI with MediatR, AutoMapper, FluentValidation
This commit is contained in:
2026-07-14 16:36:41 +02:00
parent 5e8e6a06fe
commit b7d65d7d5c
4 changed files with 101 additions and 44 deletions

View File

@@ -3,17 +3,58 @@ using System.Linq.Expressions;
namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories;
/// <summary>
/// Base repository interface for common CRUD operations.
/// Base repository interface for common CRUD operations with AutoMapper support.
/// Changes are automatically saved after each operation - no explicit SaveChanges needed.
/// </summary>
/// <typeparam name="T">Entity type</typeparam>
public interface IRepository<T> where T : class
/// <typeparam name="TEntity">Entity type</typeparam>
public interface IRepository<TEntity> where TEntity : class
{
Task<T?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IEnumerable<T>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default);
Task<T> AddAsync(T entity, CancellationToken cancellationToken = default);
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default);
Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null, CancellationToken cancellationToken = default);
// ==================== QUERY OPERATIONS ====================
Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
Task<int> CountAsync(Expression<Func<TEntity, bool>>? predicate = null, CancellationToken cancellationToken = default);
// ==================== CREATE OPERATIONS ====================
/// <summary>
/// Create entity from DTO using AutoMapper and save immediately.
/// </summary>
/// <returns>Created entity with ID populated</returns>
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancellationToken = default) where TDto : class;
// ==================== UPDATE OPERATIONS ====================
/// <summary>
/// Update ALL entities matching expression using DTO via AutoMapper.
/// WARNING: This can update multiple records. Use UpdateSingleAsync for single-record updates.
/// </summary>
/// <returns>Number of entities updated</returns>
Task<int> UpdateAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) where TDto : class;
/// <summary>
/// Update SINGLE entity matching expression using DTO via AutoMapper.
/// SAFETY: Throws exception if zero or multiple entities match the predicate.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when zero or multiple entities match</exception>
Task UpdateSingleAsync<TDto>(Expression<Func<TEntity, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) where TDto : class;
// ==================== DELETE OPERATIONS ====================
/// <summary>
/// Delete ALL entities matching expression.
/// WARNING: This can delete multiple records. Use DeleteSingleAsync for single-record deletes.
/// </summary>
/// <returns>Number of entities deleted</returns>
Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
/// <summary>
/// Delete SINGLE entity matching expression.
/// SAFETY: Throws exception if zero or multiple entities match the predicate.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when zero or multiple entities match</exception>
Task DeleteSingleAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
}