Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public interface IActivityService
List<ActivityEntity> GetPendingActivities(int editionId);
List<ActivityEntity> GetLastThreeActivities(int editionId);
IEnumerable<ActivityEntity> GetActiveActivitiesFiltered(int typeId, int editionId, List<int> timeframes, string title, int skipByPagination, int activitiesPerPage, int userFav = default);
List<ActivityEntity> GetFavouriteActivitiesByUser(int userId, int editionId);
bool AddFavouriteActivity(int activityId, int userId);
bool RemoveFavouriteActivity(int activityId, int userId);

int ChangeActivityStatus(int activityId, int statusId);
int DeleteActivity(int activityId);
Expand Down
99 changes: 96 additions & 3 deletions TaleEngine/TaleEngine.Application/ActivityService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,98 @@ public IEnumerable<ActivityEntity> GetActiveActivitiesFiltered(int typeId, int e
return query;
}

public List<ActivityEntity> GetFavouriteActivitiesByUser(int userId, int editionId)
{
if (userId == default || editionId == default)
{
return new List<ActivityEntity>();
}

return GetFavActivitiesFiltered(0, editionId, null, null, userId).ToList();
}

public bool AddFavouriteActivity(int activityId, int userId)
{
try
{
var user = _unitOfWork.UserRepository.GetById(userId);
var activity = _unitOfWork.ActivityRepository.GetById(activityId);

if (user == null || activity == null)
{
return false;
}

activity = _unitOfWork.ActivityRepository.GetAllIncludeFavs(activity.EditionId)
.FirstOrDefault(a => a.Id == activityId);

if (activity == null)
{
return false;
}

Comment on lines +92 to +99
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddFavouriteActivity loads the full list of activities (with UsersFav included) for the edition via GetAllIncludeFavs(...) and then filters in-memory with FirstOrDefault. This can become expensive as activities/users grow and is unnecessary when you only need a single activity. Prefer a repository method that fetches a single activity by id with UsersFav included (or change GetAllIncludeFavs to return an IQueryable so the filter runs in SQL).

Suggested change
activity = _unitOfWork.ActivityRepository.GetAllIncludeFavs(activity.EditionId)
.FirstOrDefault(a => a.Id == activityId);
if (activity == null)
{
return false;
}

Copilot uses AI. Check for mistakes.
if (activity.UsersFav == null)
{
activity.UsersFav = new List<UserEntity>();
}

if (activity.UsersFav.Any(u => u.Id == userId))
{
return false;
}

activity.UsersFav.Add(user);

_unitOfWork.ActivityRepository.Update(activity);
_unitOfWork.ActivityRepository.Save();

return true;
}
catch (Exception)
{
return false;
}
}

public bool RemoveFavouriteActivity(int activityId, int userId)
{
try
{
var activity = _unitOfWork.ActivityRepository.GetById(activityId);

if (activity == null)
{
return false;
}

activity = _unitOfWork.ActivityRepository.GetAllIncludeFavs(activity.EditionId)
.FirstOrDefault(a => a.Id == activityId);

if (activity?.UsersFav == null)
Comment on lines +134 to +137
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveFavouriteActivity calls GetAllIncludeFavs(activity.EditionId) (materializing all activities in the edition with favorites) and then filters by id in-memory. This is potentially very costly and can be replaced by a query that fetches just the targeted activity with UsersFav included.

Suggested change
activity = _unitOfWork.ActivityRepository.GetAllIncludeFavs(activity.EditionId)
.FirstOrDefault(a => a.Id == activityId);
if (activity?.UsersFav == null)
if (activity.UsersFav == null)

Copilot uses AI. Check for mistakes.
{
return false;
}

var userToRemove = activity.UsersFav.FirstOrDefault(u => u.Id == userId);

if (userToRemove == null)
{
return false;
}

activity.UsersFav.Remove(userToRemove);

_unitOfWork.ActivityRepository.Update(activity);
_unitOfWork.ActivityRepository.Save();

return true;
}
catch (Exception)
{
return false;
}
}

public int DeleteActivity(int activityId)
{
try
Expand Down Expand Up @@ -195,13 +287,14 @@ private IEnumerable<ActivityEntity> GetActiveActivitiesWithFilter(int type, List

private IEnumerable<ActivityEntity> GetFavActivitiesFiltered(int type, int editionId, List<int> timeframes, string title, int userFav)
{
var user = _unitOfWork.UserRepository.GetById(userFav);

var activeStatus = _activityStatusService
.GetById((int)ActivityStatusEnum.ACT);

var query = _unitOfWork.ActivityRepository.GetAllIncludeFavs(editionId)
.Where(a => a.UsersFav.Contains(user) && a.StatusId == activeStatus.Id);
.Where(a => a.EditionId == editionId
&& a.StatusId == activeStatus.Id
Comment on lines +294 to +295
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFavActivitiesFiltered starts from GetAllIncludeFavs(editionId), which already applies an edition filter when editionId is non-default, and then immediately filters a.EditionId == editionId again. This redundancy makes the intent harder to read and risks diverging if repository filtering logic changes. Consider removing the duplicate edition predicate (or, more broadly, pushing the whole filter down into the repository so it runs in the database).

Suggested change
.Where(a => a.EditionId == editionId
&& a.StatusId == activeStatus.Id
.Where(a => a.StatusId == activeStatus.Id

Copilot uses AI. Check for mistakes.
&& a.UsersFav != null
&& a.UsersFav.Any(u => u.Id == userFav));

return ApplyActivityFilters(query, type, timeframes, title);
}
Expand Down
10 changes: 10 additions & 0 deletions TaleEngine/TaleEngine.Bussiness/Commands/ActivityCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,15 @@ public bool LeaveActivityCommand(ActivityEnrollmentRequest request)
{
return _activityService.RemoveUserFromActivity(request.ActivityId, request.UserId);
}

public bool AddFavouriteActivityCommand(ActivityEnrollmentRequest request)
{
return _activityService.AddFavouriteActivity(request.ActivityId, request.UserId);
}

public bool RemoveFavouriteActivityCommand(ActivityEnrollmentRequest request)
{
return _activityService.RemoveFavouriteActivity(request.ActivityId, request.UserId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public interface IActivityCommands
void ChangeActivityStatusCommand(int activityId, int statusId);
ActivityEnrollmentResult EnrollInActivityCommand(ActivityEnrollmentRequest request);
bool LeaveActivityCommand(ActivityEnrollmentRequest request);
bool AddFavouriteActivityCommand(ActivityEnrollmentRequest request);
bool RemoveFavouriteActivityCommand(ActivityEnrollmentRequest request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface IActivityQueries
List<ActivityDto> ActiveActivitiesQuery(int editionId);
List<ActivityDto> PendingActivitiesQuery(int editionId);
ActivityFilteredResult ActiveActivitiesFilteredQuery(ActivityFilterRequest activityFilterRequest, int userId = default);
List<ActivityDto> FavouriteActivitiesByUserQuery(int userId, int editionId);
List<ActivityDto> LastThreeActivitiesQuery(int editionId);
WaitingListResult GetWaitingListQuery(int activityId);
int? GetUserPositionInWaitingListQuery(int activityId, int userId);
Expand Down
9 changes: 9 additions & 0 deletions TaleEngine/TaleEngine.Bussiness/Queries/ActivityQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ public List<ActivityDto> LastThreeActivitiesQuery(int edition)
return result;
}

public List<ActivityDto> FavouriteActivitiesByUserQuery(int userId, int editionId)
{
var activities = _activityService.GetFavouriteActivitiesByUser(userId, editionId);

var models = ActivityMapper.MapEntityToDto(activities);

return models;
}

public WaitingListResult GetWaitingListQuery(int activityId)
{
var activity = _activityService.GetById(activityId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace TaleEngine.Data.Contracts.Repositories
{
public interface IActivityRepository : IGenericRepository<ActivityEntity>
{
List<ActivityEntity> GetAllIncludeFavs(int eventId);
List<ActivityEntity> GetAllIncludeFavs(int editionId);
List<ActivityEntity> GetEventActivities(int eventId);
List<ActivityEntity> GetActivitiesByStatus(int edition, int status);
List<ActivityEntity> GetActiveActivitiesFiltered(int status, int type, int edition, string title, int skip, int activitiesPerPage);
Expand Down
14 changes: 10 additions & 4 deletions TaleEngine/TaleEngine.Data/Repositories/ActivityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,18 @@ public int GetTotalActivities(int status, int type, int edition, string title)
throw new System.NotImplementedException();
}

public List<ActivityEntity> GetAllIncludeFavs(int eventId)
public List<ActivityEntity> GetAllIncludeFavs(int editionId)
{
return _context.Activities
var query = _context.Activities
.Include(a => a.UsersFav)
.Where(a => !a.IsDeleted)
.ToList();
.Where(a => !a.IsDeleted);

if (editionId != default)
{
query = query.Where(a => a.EditionId == editionId);
}

return query.ToList();
}

public List<ActivityEntity> GetAllIncludeEnrollments(int activityId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,179 @@ public void ChangeActivityStatus_Success()
commands.Verify(x => x.ChangeActivityStatusCommand(dto.StatusId, dto.ActivityId), Times.Once);
}

[Fact]
public void GetFavouriteActivitiesByUser_Success()
{
// Arrange
int userId = 1;
int editionId = 2;
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
List<ActivityDto> dto = new()
{
new ActivityDto()
};

queries.Setup(x => x.FavouriteActivitiesByUserQuery(userId, editionId))
.Returns(dto);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.GetFavouriteActivitiesByUser(userId, editionId);

// Assert
var resultAsObjResult = result as ObjectResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status200OK);

queries.Verify(x => x.FavouriteActivitiesByUserQuery(userId, editionId), Times.Once);
}

[Fact]
public void GetFavouriteActivitiesByUser_EmptyResult_Success()
{
// Arrange
int userId = 1;
int editionId = 2;
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
List<ActivityDto> dto = new();

queries.Setup(x => x.FavouriteActivitiesByUserQuery(userId, editionId))
.Returns(dto);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.GetFavouriteActivitiesByUser(userId, editionId);

// Assert
var resultAsObjResult = result as StatusCodeResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status204NoContent);

queries.Verify(x => x.FavouriteActivitiesByUserQuery(userId, editionId), Times.Once);
}

[Fact]
public void AddFavouriteActivity_Success()
{
// Arrange
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
ActivityEnrollmentRequest request = new()
{
ActivityId = 1,
UserId = 1
};

commands.Setup(x => x.AddFavouriteActivityCommand(request))
.Returns(true);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.AddFavouriteActivity(request);

// Assert
var resultAsObjResult = result as ObjectResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status200OK);

commands.Verify(x => x.AddFavouriteActivityCommand(request), Times.Once);
}

[Fact]
public void AddFavouriteActivity_Fail_Success()
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name AddFavouriteActivity_Fail_Success is contradictory (it’s a failure-path test expecting 400). Renaming it to reflect the behavior under test (e.g., AddFavouriteActivity_Failure_ReturnsBadRequest) will make the test suite easier to scan and maintain.

Suggested change
public void AddFavouriteActivity_Fail_Success()
public void AddFavouriteActivity_Failure_ReturnsBadRequest()

Copilot uses AI. Check for mistakes.
{
// Arrange
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
ActivityEnrollmentRequest request = new()
{
ActivityId = 1,
UserId = 1
};

commands.Setup(x => x.AddFavouriteActivityCommand(request))
.Returns(false);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.AddFavouriteActivity(request);

// Assert
var resultAsObjResult = result as ObjectResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status400BadRequest);

commands.Verify(x => x.AddFavouriteActivityCommand(request), Times.Once);
}

[Fact]
public void RemoveFavouriteActivity_Success()
{
// Arrange
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
ActivityEnrollmentRequest request = new()
{
ActivityId = 1,
UserId = 1
};

commands.Setup(x => x.RemoveFavouriteActivityCommand(request))
.Returns(true);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.RemoveFavouriteActivity(request);

// Assert
var resultAsObjResult = result as ObjectResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status200OK);

commands.Verify(x => x.RemoveFavouriteActivityCommand(request), Times.Once);
}

[Fact]
public void RemoveFavouriteActivity_Fail_Success()
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name RemoveFavouriteActivity_Fail_Success is contradictory (it’s a failure-path test expecting 400). Consider renaming it to something like RemoveFavouriteActivity_Failure_ReturnsBadRequest for clarity.

Suggested change
public void RemoveFavouriteActivity_Fail_Success()
public void RemoveFavouriteActivity_Failure_ReturnsBadRequest()

Copilot uses AI. Check for mistakes.
{
// Arrange
Mock<IActivityCommands> commands = new();
Mock<IActivityQueries> queries = new();
ActivityEnrollmentRequest request = new()
{
ActivityId = 1,
UserId = 1
};

commands.Setup(x => x.RemoveFavouriteActivityCommand(request))
.Returns(false);

ActivityController target = new(commands.Object, queries.Object);

// Act
IActionResult result = target.RemoveFavouriteActivity(request);

// Assert
var resultAsObjResult = result as ObjectResult;

result.Should().NotBeNull();
resultAsObjResult.StatusCode.Should().Be(StatusCodes.Status400BadRequest);

commands.Verify(x => x.RemoveFavouriteActivityCommand(request), Times.Once);
}

[Fact]
public void GetActivitiesFiltered_Success()
{
Expand Down
Loading
Loading