diff --git a/src/Koogle.Application/Services/ClubService.cs b/src/Koogle.Application/Services/ClubService.cs
index c38f8c2..20435be 100644
--- a/src/Koogle.Application/Services/ClubService.cs
+++ b/src/Koogle.Application/Services/ClubService.cs
@@ -3,6 +3,7 @@ using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Domain.Entities;
using Koogle.Domain.Interfaces;
+using Koogle.Infrastructure.Repositories;
using KoogleApp.Data;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
@@ -48,6 +49,12 @@ public class ClubService : IClubService
///
public async Task CreateAsync(CreateClubDto dto, CancellationToken ct = default)
{
+ var existingClub = await _clubRepository.GetByNameAsync(dto.Name, null, ct);
+ if (existingClub != null)
+ {
+ throw new InvalidOperationException($"Club with name '{dto.Name}' already exists");
+ }
+
var entity = new Club
{
Id = Guid.NewGuid(),
@@ -67,6 +74,12 @@ public class ClubService : IClubService
var existing = await _clubRepository.GetByIdAsync(dto.Id, ct)
?? throw new InvalidOperationException($"Club with id {dto.Id} not found.");
+ var existingClub = await _clubRepository.GetByNameAsync(dto.Name, dto.Id, ct);
+ if (existingClub != null)
+ {
+ throw new InvalidOperationException($"Club with name '{dto.Name}' already exists");
+ }
+
existing.Name = dto.Name;
existing.ExpenseCalculation = dto.ExpenseCalculation;
existing.ModifiedAt = DateTime.UtcNow;
diff --git a/src/Koogle.Application/Services/ExpenseService.cs b/src/Koogle.Application/Services/ExpenseService.cs
index d2bb12a..5aa3e74 100644
--- a/src/Koogle.Application/Services/ExpenseService.cs
+++ b/src/Koogle.Application/Services/ExpenseService.cs
@@ -3,6 +3,7 @@ using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Domain.Entities;
using Koogle.Domain.Interfaces;
+using Koogle.Infrastructure.Repositories;
namespace Koogle.Application.Services;
@@ -51,6 +52,12 @@ public class ExpenseService : IExpenseService
///
public async Task CreateAsync(CreateExpenseDto dto, CancellationToken ct = default)
{
+ var existingExpense = await _expenseRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, null, ct);
+ if (existingExpense != null)
+ {
+ throw new InvalidOperationException($"Expense with name '{dto.Name}' already exists");
+ }
+
var entity = new Expense
{
Id = Guid.NewGuid(),
@@ -79,6 +86,12 @@ public class ExpenseService : IExpenseService
if (existing.ClubId != _clubContext.ClubId)
throw new InvalidOperationException("Expense does not belong to current club.");
+ var existingExpense = await _expenseRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, dto.Id, ct);
+ if (existingExpense != null)
+ {
+ throw new InvalidOperationException($"Expense with name '{dto.Name}' already exists");
+ }
+
existing.Name = dto.Name;
existing.Description = dto.Description;
existing.Price = dto.Price;
diff --git a/src/Koogle.Application/Services/PersonService.cs b/src/Koogle.Application/Services/PersonService.cs
index 8040b0b..1e4d202 100644
--- a/src/Koogle.Application/Services/PersonService.cs
+++ b/src/Koogle.Application/Services/PersonService.cs
@@ -52,7 +52,7 @@ public class PersonService : IPersonService
///
public async Task CreateAsync(CreatePersonDto dto, CancellationToken ct = default)
{
- var existingPerson = await _personRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, ct);
+ var existingPerson = await _personRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, null, ct);
if (existingPerson != null)
{
throw new InvalidOperationException($"Person with name '{dto.Name}' already exists");
@@ -81,7 +81,7 @@ public class PersonService : IPersonService
if (existing.ClubId != _clubContext.ClubId)
throw new InvalidOperationException("Person does not belong to current club.");
- var existingPerson = await _personRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, ct);
+ var existingPerson = await _personRepository.GetByNameAsync(_clubContext.ClubId, dto.Name, dto.Id, ct);
if (existingPerson != null)
{
throw new InvalidOperationException($"Person with name '{dto.Name}' already exists");
diff --git a/src/Koogle.Domain/Interfaces/IClubRepository.cs b/src/Koogle.Domain/Interfaces/IClubRepository.cs
index 6133f45..4b2a4f4 100644
--- a/src/Koogle.Domain/Interfaces/IClubRepository.cs
+++ b/src/Koogle.Domain/Interfaces/IClubRepository.cs
@@ -45,4 +45,13 @@ public interface IClubRepository
/// Cancellation token.
/// True if deleted successfully; otherwise, false.
Task DeleteAsync(Guid id, CancellationToken ct = default);
+
+ ///
+ /// Get a club by its name within a specific context.
+ ///
+ /// Name to search for
+ /// A unique ID to exclude from search
+ /// Cancellation token.
+ ///
+ Task GetByNameAsync(string clubName, Guid? excludeGuid, CancellationToken ct = default);
}
diff --git a/src/Koogle.Domain/Interfaces/IExpenseRepository.cs b/src/Koogle.Domain/Interfaces/IExpenseRepository.cs
index 40a9d6a..c1b0c9b 100644
--- a/src/Koogle.Domain/Interfaces/IExpenseRepository.cs
+++ b/src/Koogle.Domain/Interfaces/IExpenseRepository.cs
@@ -46,4 +46,14 @@ public interface IExpenseRepository
/// Cancellation token.
/// True if deleted successfully; otherwise, false.
Task DeleteAsync(Guid id, CancellationToken ct = default);
+
+ ///
+ /// Read an expense by its name within a specific club context.
+ ///
+ ///
+ ///
+ /// Guid of an expense, that should be excluded
+ ///
+ /// The expense with the name or null
+ Task GetByNameAsync(Guid clubId, string expenseName, Guid? excludeGuid, CancellationToken ct = default);
}
diff --git a/src/Koogle.Domain/Interfaces/IPersonRepository.cs b/src/Koogle.Domain/Interfaces/IPersonRepository.cs
index 957b466..11da9fe 100644
--- a/src/Koogle.Domain/Interfaces/IPersonRepository.cs
+++ b/src/Koogle.Domain/Interfaces/IPersonRepository.cs
@@ -50,9 +50,10 @@ public interface IPersonRepository
///
/// Read a person by its name within a specific club context.
///
- ///
- ///
+ ///
+ ///
+ /// Guid of a person, that should be excluded
///
/// The person with the name or null
- Task GetByNameAsync(Guid clubContextClubId, string dtoName, CancellationToken ct);
+ Task GetByNameAsync(Guid clubId, string personName, Guid? excludeGuid, CancellationToken ct = default);
}
diff --git a/src/Koogle.Infrastructure/Repositories/ClubRepository.cs b/src/Koogle.Infrastructure/Repositories/ClubRepository.cs
index 4c84e4c..b80d549 100644
--- a/src/Koogle.Infrastructure/Repositories/ClubRepository.cs
+++ b/src/Koogle.Infrastructure/Repositories/ClubRepository.cs
@@ -61,4 +61,18 @@ public class ClubRepository(IDbContextFactory contextFactory) : IC
await context.SaveChangesAsync(ct);
return true;
}
+
+ ///
+ public async Task GetByNameAsync(string clubName, Guid? excludeGuid, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ var query = context.Clubs
+ .Where(c => c.Name.ToLower() == clubName.ToLower() && !c.IsDeleted);
+ if (excludeGuid.HasValue)
+ {
+ query = query.Where(c => c.Id != excludeGuid.Value);
+ }
+ var entity = await query.FirstOrDefaultAsync(ct);
+ return entity;
+ }
}
diff --git a/src/Koogle.Infrastructure/Repositories/ExpenseRepository.cs b/src/Koogle.Infrastructure/Repositories/ExpenseRepository.cs
index 0d18ea7..fb6061b 100644
--- a/src/Koogle.Infrastructure/Repositories/ExpenseRepository.cs
+++ b/src/Koogle.Infrastructure/Repositories/ExpenseRepository.cs
@@ -63,4 +63,18 @@ public class ExpenseRepository(IDbContextFactory contextFactory) :
await context.SaveChangesAsync(ct);
return true;
}
+
+ ///
+ public async Task GetByNameAsync(Guid clubId, string expenseName, Guid? excludeGuid, CancellationToken ct = default)
+ {
+ await using var context = await contextFactory.CreateDbContextAsync(ct);
+ var query = context.Expenses
+ .Where(e => e.ClubId == clubId && !e.IsDeleted && e.Name.ToLower() == expenseName.ToLower());
+ if (excludeGuid.HasValue)
+ {
+ query = query.Where(e => e.Id != excludeGuid.Value);
+ }
+ var entity = await query.FirstOrDefaultAsync(ct);
+ return entity;
+ }
}
diff --git a/src/Koogle.Infrastructure/Repositories/PersonRepository.cs b/src/Koogle.Infrastructure/Repositories/PersonRepository.cs
index cfb60a0..b8e6eb9 100644
--- a/src/Koogle.Infrastructure/Repositories/PersonRepository.cs
+++ b/src/Koogle.Infrastructure/Repositories/PersonRepository.cs
@@ -65,11 +65,22 @@ public class PersonRepository(IDbContextFactory contextFactory) :
}
///
- public async Task GetByNameAsync(Guid clubContextClubId, string dtoName, CancellationToken ct)
+ public async Task GetByNameAsync(Guid clubId, string personName, Guid? excludeGuid, CancellationToken ct = default)
{
await using var context = await contextFactory.CreateDbContextAsync(ct);
- var entity = await context.Persons
- .FirstOrDefaultAsync(p => p.ClubId == clubContextClubId && p.Name == dtoName && !p.IsDeleted, ct);
+ Person? entity;
+ if (excludeGuid != null)
+ {
+ entity = await context.Persons
+ .FirstOrDefaultAsync(
+ p => p.ClubId == clubId && p.Name.ToLower() == personName.ToLower() && !p.IsDeleted && p.Id != excludeGuid, ct);
+ } else
+ {
+ entity = await context.Persons
+ .FirstOrDefaultAsync(
+ p => p.ClubId == clubId && p.Name.ToLower() == personName.ToLower() && !p.IsDeleted, ct);
+ }
+
return entity;
}
}