Skip to content

Commit f762520

Browse files
committed
Activity logging
1 parent aed1d74 commit f762520

File tree

13 files changed

+421
-11
lines changed

13 files changed

+421
-11
lines changed

backend/src/Contact.Api/Controllers/UsersController.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ namespace Contact.Api.Controllers;
1111
public class UsersController : ControllerBase
1212
{
1313
private IUserService _userService;
14+
private readonly IActivityLogService _activityLogService;
1415

15-
public UsersController(IUserService userService)
16+
public UsersController(IUserService userService, IActivityLogService activityLogService)
1617
{
1718
_userService = userService;
19+
_activityLogService = activityLogService;
1820
}
1921

2022
[HttpPost("register")]
@@ -104,4 +106,12 @@ public async Task<IActionResult> ChangePassword(ChangePassword changePassword)
104106
var result = await _userService.ChangePassword(changePassword);
105107
return Ok(result);
106108
}
107-
}
109+
110+
[HttpGet("activity-logs")]
111+
[Authorize]
112+
public async Task<IActionResult> GetActivityLogs([FromQuery] string username, [FromQuery] string email)
113+
{
114+
var logs = await _activityLogService.GetActivityLogsAsync(username, email);
115+
return Ok(logs);
116+
}
117+
}

backend/src/Contact.Api/Core/Middleware/ActivityLoggingMiddleware.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public async Task InvokeAsync(HttpContext context)
2929
var activityLogService = scope.ServiceProvider.GetRequiredService<IActivityLogService>();
3030
var userIdClaim = context.User.FindFirst("id");
3131
var userId = userIdClaim != null ? Guid.Parse(userIdClaim.Value) : Guid.Empty;
32+
var username = context.User.Identity?.Name;
33+
var email = context.User.FindFirst(ClaimTypes.Email)?.Value;
3234
var activityDescription = activityAttribute.ActivityDescription;
3335
var endpointPath = context.Request.Path;
3436
var httpMethod = context.Request.Method;
@@ -38,6 +40,8 @@ public async Task InvokeAsync(HttpContext context)
3840
var logEntry = new ActivityLogEntry
3941
{
4042
UserId = userId,
43+
Username = username,
44+
Email = email,
4145
Activity = activityDescription,
4246
Endpoint = endpointPath,
4347
HttpMethod = httpMethod,

backend/src/Contact.Application/Interfaces/IActivityLogService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ namespace Contact.Application.Interfaces;
55
public interface IActivityLogService
66
{
77
Task LogActivityAsync(ActivityLogEntry logEntry);
8+
Task<IEnumerable<ActivityLogEntry>> GetActivityLogsAsync(string username, string email);
89
}

backend/src/Contact.Application/Services/ActivityLogService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ public async Task LogActivityAsync(ActivityLogEntry logEntry)
1919
{
2020
await _activityLogRepository.LogActivityAsync(logEntry);
2121
}
22+
23+
public async Task<IEnumerable<ActivityLogEntry>> GetActivityLogsAsync(string username, string email)
24+
{
25+
return await _activityLogRepository.GetActivityLogsAsync(username, email);
26+
}
2227
}

backend/src/Contact.Domain/Entities/ActivityLogEntry.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
public class ActivityLogEntry
44
{
55
public Guid UserId { get; set; }
6+
public required string Username { get; set; }
7+
public required string Email { get; set; }
68
public required string Activity { get; set; }
79
public required string Endpoint { get; set; }
810
public required string HttpMethod { get; set; }

backend/src/Contact.Domain/Interfaces/IActivityLogRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace Contact.Domain.Interfaces;
44

5-
65
public interface IActivityLogRepository
76
{
87
Task LogActivityAsync(ActivityLogEntry logEntry);
8+
Task<IEnumerable<ActivityLogEntry>> GetActivityLogsAsync(string username, string email);
99
}

backend/src/Contact.Infrastructure/Persistence/Repositories/ActivityLogRepository.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,32 @@ public async Task LogActivityAsync(ActivityLogEntry logEntry)
1818
{
1919
var dbPara = new DynamicParameters();
2020
dbPara.Add("UserId", logEntry.UserId);
21+
dbPara.Add("Username", logEntry.Username);
22+
dbPara.Add("Email", logEntry.Email);
2123
dbPara.Add("Activity", logEntry.Activity);
2224
dbPara.Add("Endpoint", logEntry.Endpoint);
2325
dbPara.Add("HttpMethod", logEntry.HttpMethod);
2426
dbPara.Add("Timestamp", logEntry.Timestamp);
2527
dbPara.Add("IpAddress", logEntry.IpAddress);
2628
dbPara.Add("UserAgent", logEntry.UserAgent);
2729
var sql = @"
28-
INSERT INTO ""ActivityLog"" (""UserId"", ""Activity"", ""Endpoint"", ""HttpMethod"", ""Timestamp"", ""IpAddress"", ""UserAgent"")
29-
VALUES (@UserId, @Activity, @Endpoint, @HttpMethod, @Timestamp, @IpAddress, @UserAgent)
30+
INSERT INTO ""ActivityLog"" (""UserId"", ""Username"", ""Email"", ""Activity"", ""Endpoint"", ""HttpMethod"", ""Timestamp"", ""IpAddress"", ""UserAgent"")
31+
VALUES (@UserId, @Username, @Email, @Activity, @Endpoint, @HttpMethod, @Timestamp, @IpAddress, @UserAgent)
3032
RETURNING *";
3133

3234
await _dapperHelper.Insert<ActivityLogEntry>(sql, dbPara, CommandType.Text);
3335
}
36+
37+
public async Task<IEnumerable<ActivityLogEntry>> GetActivityLogsAsync(string username, string email)
38+
{
39+
var dbPara = new DynamicParameters();
40+
dbPara.Add("Username", username);
41+
dbPara.Add("Email", email);
42+
var sql = @"
43+
SELECT * FROM ""ActivityLog""
44+
WHERE (""Username"" = @Username OR @Username IS NULL)
45+
AND (""Email"" = @Email OR @Email IS NULL)";
46+
47+
return await _dapperHelper.Query<ActivityLogEntry>(sql, dbPara, CommandType.Text);
48+
}
3449
}

frontend/src/app/@core/services/user.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,10 @@ export class UserService {
6161
delete(_id: string) {
6262
return this.http.delete(environment.apiEndpoint + "/user/" + _id);
6363
}
64+
65+
getActivityLogs(username: string, email: string) {
66+
return this.http.get<any[]>(`${environment.apiEndpoint}/users/activity-logs`, {
67+
params: { username, email }
68+
});
69+
}
6470
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<div class="container mx-auto p-4">
2+
<div class="max-w-6xl mx-auto mb-6 bg-primary text-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
3+
<div class="p-6 flex justify-between items-center">
4+
<h1 class="text-2xl font-bold">Activity Logs</h1>
5+
</div>
6+
</div>
7+
8+
<div class="max-w-6xl mx-auto mb-6 bg-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
9+
<div class="p-6">
10+
<form [formGroup]="filterForm" (ngSubmit)="onFilter()">
11+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
12+
<div>
13+
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
14+
<input type="text" id="username" formControlName="username" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
15+
</div>
16+
<div>
17+
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
18+
<input type="email" id="email" formControlName="email" class="mt-1 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
19+
</div>
20+
</div>
21+
<div class="mt-4">
22+
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-primary hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
23+
Filter
24+
</button>
25+
</div>
26+
</form>
27+
</div>
28+
</div>
29+
30+
<div class="max-w-6xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
31+
<div class="p-6">
32+
<table class="min-w-full divide-y divide-gray-200">
33+
<thead class="bg-gray-50">
34+
<tr>
35+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Username</th>
36+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
37+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Activity</th>
38+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Endpoint</th>
39+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Timestamp</th>
40+
</tr>
41+
</thead>
42+
<tbody class="bg-white divide-y divide-gray-200">
43+
<tr *ngFor="let log of activityLogs">
44+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.username }}</td>
45+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.email }}</td>
46+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.activity }}</td>
47+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.endpoint }}</td>
48+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ log.timestamp | date: 'short' }}</td>
49+
</tr>
50+
</tbody>
51+
</table>
52+
</div>
53+
</div>
54+
</div>

0 commit comments

Comments
 (0)