Skip to content

Commit d49d13f

Browse files
committed
feat(api): add mineOnly activity feed filter and enrich project members with email
1 parent f0021f6 commit d49d13f

15 files changed

Lines changed: 118 additions & 23 deletions

File tree

src/TaskManagement.Api/Features/Activity/Controllers/ActivityController.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,24 @@ public async Task<IActionResult> Get(
4444
[FromQuery] Guid? projectId,
4545
[FromQuery] int? limit,
4646
[FromQuery] int page = 1,
47-
[FromQuery] int pageSize = 50)
47+
[FromQuery] int pageSize = 50,
48+
[FromQuery] bool mineOnly = false)
4849
{
4950
_logger.LogInformation(
50-
"Retrieving activity feed. ProjectId: {ProjectId}, Limit: {Limit}, Page: {Page}, PageSize: {PageSize}",
51+
"Retrieving activity feed. ProjectId: {ProjectId}, Limit: {Limit}, Page: {Page}, PageSize: {PageSize}, MineOnly: {MineOnly}",
5152
projectId,
5253
limit,
5354
page,
54-
pageSize);
55+
pageSize,
56+
mineOnly);
5557

5658
var result = await _mediator.Send(new GetActivityFeedQuery
5759
{
5860
ProjectId = projectId,
5961
Limit = limit,
6062
Page = page,
61-
PageSize = pageSize
63+
PageSize = pageSize,
64+
MineOnly = mineOnly
6265
});
6366

6467
return Ok(result);

src/TaskManagement.Api/Features/Activity/Queries/GetActivityFeedQuery.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ public class GetActivityFeedQuery : IRequest<IReadOnlyList<ActivityLogDto>>
99
public int? Limit { get; set; }
1010
public int Page { get; set; } = 1;
1111
public int PageSize { get; set; } = 50;
12+
public bool MineOnly { get; set; }
1213
}
1314
}

src/TaskManagement.Api/Features/Activity/Queries/Handlers/GetActivityFeedQueryHandler.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public async Task<IReadOnlyList<ActivityLogDto>> Handle(GetActivityFeedQuery req
9090
}
9191
}
9292

93+
if (request.MineOnly)
94+
{
95+
query = query.Where(activity => activity.CreatedByUserId == currentUserId);
96+
}
97+
9398
var activityLogs = await query
9499
.OrderByDescending(activity => activity.CreatedAt)
95100
.Skip((page - 1) * pageSize)

src/TaskManagement.Api/Features/Projects/Mappings/ProjectMappingProfile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public ProjectMappingProfile()
1313
.ForMember(dest => dest.TaskItems, opt => opt.MapFrom(src => src.TaskItems));
1414

1515
CreateMap<CreateProjectCommand, Project>()
16+
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description ?? string.Empty))
1617
.ForMember(dest => dest.Id, opt => opt.Ignore())
1718
.ForMember(dest => dest.OwnerUserId, opt => opt.Ignore())
1819
.ForMember(dest => dest.Members, opt => opt.Ignore())

src/TaskManagement.Api/Features/Projects/Models/DTOs/ProjectMemberDto.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public class ProjectMemberDto
44
{
55
public string UserId { get; set; } = string.Empty;
66
public string DisplayName { get; set; } = string.Empty;
7+
public string? Email { get; set; }
78
public bool IsOwner { get; set; }
89
}
910
}

src/TaskManagement.Api/Features/Projects/Queries/Handlers/GetProjectMembersQueryHandler.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,24 @@ public async Task<IReadOnlyList<ProjectMemberDto>> Handle(GetProjectMembersQuery
6060
.ToHashSet(StringComparer.OrdinalIgnoreCase);
6161
members.Add(project.OwnerUserId);
6262

63-
var displayNameEntries = await Task.WhenAll(
63+
var userSummaryEntries = await Task.WhenAll(
6464
members.Select(async userId => new
6565
{
6666
UserId = userId,
67-
DisplayName = await _userDirectoryService.GetDisplayNameAsync(userId, cancellationToken)
67+
UserSummary = await _userDirectoryService.GetUserSummaryAsync(userId, cancellationToken)
6868
}));
6969

70-
var displayNames = displayNameEntries.ToDictionary(
70+
var userSummaries = userSummaryEntries.ToDictionary(
7171
entry => entry.UserId,
72-
entry => entry.DisplayName,
72+
entry => entry.UserSummary,
7373
StringComparer.OrdinalIgnoreCase);
7474

7575
var result = members
7676
.Select(userId => new ProjectMemberDto
7777
{
7878
UserId = userId,
79-
DisplayName = displayNames[userId] ?? userId,
79+
DisplayName = userSummaries[userId]?.DisplayName ?? userId,
80+
Email = userSummaries[userId]?.Email,
8081
IsOwner = string.Equals(userId, project.OwnerUserId, StringComparison.OrdinalIgnoreCase)
8182
})
8283
.OrderByDescending(member => member.IsOwner)

src/TaskManagement.Api/Features/TaskItems/Commands/Handlers/CreateTaskItemCommandHandler.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ public async Task<TaskItemDto> Handle(CreateTaskItemCommand request, Cancellatio
9595
await _dbContext.SaveChangesAsync(cancellationToken);
9696
await _activityPublisher.PublishAsync(activityLog, cancellationToken);
9797

98-
return _mapper.Map<TaskItemDto>(taskItem);
98+
var result = _mapper.Map<TaskItemDto>(taskItem);
99+
result.AssignedUserName = await ResolveAssigneeDisplayNameAsync(taskItem.AssignedUserId, cancellationToken);
100+
return result;
99101
}
100102

101103
private static string? NormalizeAssignedUserId(string? assignedUserId)
@@ -129,5 +131,16 @@ private static void EnsureProjectMember(
129131
project.Members.Add(member);
130132
dbContext.ProjectMembers.Add(member);
131133
}
134+
135+
private async Task<string> ResolveAssigneeDisplayNameAsync(string? assignedUserId, CancellationToken cancellationToken)
136+
{
137+
if (string.IsNullOrWhiteSpace(assignedUserId))
138+
{
139+
return "Unassigned";
140+
}
141+
142+
var displayName = await _userDirectoryService.GetDisplayNameAsync(assignedUserId, cancellationToken);
143+
return string.IsNullOrWhiteSpace(displayName) ? assignedUserId : displayName;
144+
}
132145
}
133146
}

src/TaskManagement.Api/Features/TaskItems/Commands/Handlers/PatchTaskItemCommandHandler.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,17 @@ public async Task<TaskItemDto> Handle(PatchTaskItemCommand request, Cancellation
125125

126126
if (!string.Equals(previousAssignedUserId, taskItem.AssignedUserId, StringComparison.Ordinal))
127127
{
128+
var oldAssigneeDisplayName = await ResolveAssigneeDisplayNameAsync(previousAssignedUserId, cancellationToken);
129+
var newAssigneeDisplayName = await ResolveAssigneeDisplayNameAsync(taskItem.AssignedUserId, cancellationToken);
128130
activityLogs.Add(new ActivityLog
129131
{
130132
Type = ActivityType.TaskAssigneeChanged,
131133
ProjectId = taskItem.ProjectId,
132134
TaskItemId = taskItem.Id,
133135
ProjectName = taskItem.Project.Name,
134136
TaskTitle = taskItem.Title,
135-
OldValue = previousAssignedUserId,
136-
NewValue = taskItem.AssignedUserId
137+
OldValue = oldAssigneeDisplayName,
138+
NewValue = newAssigneeDisplayName
137139
});
138140
}
139141

@@ -162,7 +164,9 @@ public async Task<TaskItemDto> Handle(PatchTaskItemCommand request, Cancellation
162164
await _activityPublisher.PublishAsync(activityLog, cancellationToken);
163165
}
164166

165-
return _mapper.Map<TaskItemDto>(taskItem);
167+
var result = _mapper.Map<TaskItemDto>(taskItem);
168+
result.AssignedUserName = await ResolveAssigneeDisplayNameAsync(taskItem.AssignedUserId, cancellationToken);
169+
return result;
166170
}
167171

168172
private static string? NormalizeAssignedUserId(string? assignedUserId)
@@ -196,5 +200,16 @@ private static void EnsureProjectMember(
196200
project.Members.Add(member);
197201
dbContext.ProjectMembers.Add(member);
198202
}
203+
204+
private async Task<string> ResolveAssigneeDisplayNameAsync(string? assignedUserId, CancellationToken cancellationToken)
205+
{
206+
if (string.IsNullOrWhiteSpace(assignedUserId))
207+
{
208+
return "Unassigned";
209+
}
210+
211+
var displayName = await _userDirectoryService.GetDisplayNameAsync(assignedUserId, cancellationToken);
212+
return string.IsNullOrWhiteSpace(displayName) ? assignedUserId : displayName;
213+
}
199214
}
200215
}

src/TaskManagement.Api/Features/TaskItems/Commands/Handlers/UpdateTaskItemCommandHandler.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,17 @@ public async Task<TaskItemDto> Handle(UpdateTaskItemCommand request, Cancellatio
118118

119119
if (!string.Equals(previousAssignedUserId, taskItem.AssignedUserId, StringComparison.Ordinal))
120120
{
121+
var oldAssigneeDisplayName = await ResolveAssigneeDisplayNameAsync(previousAssignedUserId, cancellationToken);
122+
var newAssigneeDisplayName = await ResolveAssigneeDisplayNameAsync(taskItem.AssignedUserId, cancellationToken);
121123
activityLogs.Add(new ActivityLog
122124
{
123125
Type = ActivityType.TaskAssigneeChanged,
124126
ProjectId = taskItem.ProjectId,
125127
TaskItemId = taskItem.Id,
126128
ProjectName = taskItem.Project.Name,
127129
TaskTitle = taskItem.Title,
128-
OldValue = previousAssignedUserId,
129-
NewValue = taskItem.AssignedUserId
130+
OldValue = oldAssigneeDisplayName,
131+
NewValue = newAssigneeDisplayName
130132
});
131133
}
132134

@@ -155,7 +157,9 @@ public async Task<TaskItemDto> Handle(UpdateTaskItemCommand request, Cancellatio
155157
await _activityPublisher.PublishAsync(activityLog, cancellationToken);
156158
}
157159

158-
return _mapper.Map<TaskItemDto>(taskItem);
160+
var result = _mapper.Map<TaskItemDto>(taskItem);
161+
result.AssignedUserName = await ResolveAssigneeDisplayNameAsync(taskItem.AssignedUserId, cancellationToken);
162+
return result;
159163
}
160164

161165
private static string? NormalizeAssignedUserId(string? assignedUserId)
@@ -189,5 +193,16 @@ private static void EnsureProjectMember(
189193
project.Members.Add(member);
190194
dbContext.ProjectMembers.Add(member);
191195
}
196+
197+
private async Task<string> ResolveAssigneeDisplayNameAsync(string? assignedUserId, CancellationToken cancellationToken)
198+
{
199+
if (string.IsNullOrWhiteSpace(assignedUserId))
200+
{
201+
return "Unassigned";
202+
}
203+
204+
var displayName = await _userDirectoryService.GetDisplayNameAsync(assignedUserId, cancellationToken);
205+
return string.IsNullOrWhiteSpace(displayName) ? assignedUserId : displayName;
206+
}
192207
}
193208
}

src/TaskManagement.Api/Features/Users/Services/AuthUserDirectoryService.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Net;
22
using System.Net.Http.Json;
33
using TaskManagement.Api.Features.Users.Services.Interfaces;
4+
using TaskManagement.Api.Features.Users.Services.Models;
45

56
namespace TaskManagement.Api.Features.Users.Services
67
{
@@ -40,6 +41,12 @@ public async Task<bool> UserExistsAsync(string userId, CancellationToken cancell
4041
}
4142

4243
public async Task<string?> GetDisplayNameAsync(string userId, CancellationToken cancellationToken)
44+
{
45+
var userSummary = await GetUserSummaryAsync(userId, cancellationToken);
46+
return userSummary?.DisplayName;
47+
}
48+
49+
public async Task<UserDirectorySummary?> GetUserSummaryAsync(string userId, CancellationToken cancellationToken)
4350
{
4451
if (string.IsNullOrWhiteSpace(userId))
4552
{
@@ -68,9 +75,15 @@ public async Task<bool> UserExistsAsync(string userId, CancellationToken cancell
6875
return null;
6976
}
7077

71-
return user?.IsActive == false
78+
var displayName = user?.IsActive == false
7279
? $"{baseDisplayName} (Inactive)"
7380
: baseDisplayName;
81+
82+
return new UserDirectorySummary
83+
{
84+
DisplayName = displayName,
85+
Email = user?.Email
86+
};
7487
}
7588

7689
private sealed class UserSummaryResponse

0 commit comments

Comments
 (0)