Skip to content

Commit 14d1e09

Browse files
committed
Activity log implementation in the UI
1 parent a140918 commit 14d1e09

File tree

16 files changed

+102
-36
lines changed

16 files changed

+102
-36
lines changed

backend/scripts/seed-data.sql

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
55
CREATE TABLE IF NOT EXISTS "ActivityLog" (
66
"Id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
77
"UserId" UUID NOT NULL,
8+
"Username" VARCHAR(250) NOT NULL,
9+
"Email" VARCHAR(250) NOT NULL,
810
"Activity" VARCHAR(250) NOT NULL,
911
"Endpoint" VARCHAR(250) NOT NULL,
1012
"HttpMethod" VARCHAR(10) NOT NULL,
@@ -132,7 +134,8 @@ INSERT INTO "Operations" ("Id", "Name", "Description", "CreatedOn", "CreatedBy")
132134
-- Insert data into Pages table
133135
INSERT INTO "Pages" ("Id", "Name", "Url", "CreatedOn", "CreatedBy") VALUES
134136
('aa56a391-e880-4ac5-9f6f-6c8aa33454b8', 'Contacts', '/contacts', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb'),
135-
('c4943131-a642-4352-9725-e44ba5972b4b', 'Users', '/users', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb');
137+
('c4943131-a642-4352-9725-e44ba5972b4b', 'Users', 'admin/users', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb'),
138+
('c4943131-a642-4352-9725-e44ba5972b4c', 'ActivityLog', 'admin/activity-logs', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb');
136139

137140
-- Insert data into Roles table
138141
INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedOn", "CreatedBy") VALUES
@@ -165,7 +168,7 @@ INSERT INTO "Permissions" ("Id", "PageId", "OperationId", "Description", "Create
165168
('c94c23ad-59d4-4f80-91ee-39316140db17', 'c4943131-a642-4352-9725-e44ba5972b4b', 'dce8d805-df41-4549-be7b-6ed5647b09c3', 'User Update', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb'),
166169
('82755e66-b743-46e2-b612-efd2db6bcd75', 'c4943131-a642-4352-9725-e44ba5972b4b', '7493f274-5007-4e17-9840-88c9a096422f', 'User Read', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb'),
167170
('82755e66-b743-46e2-b612-efd2db6bce75', 'c4943131-a642-4352-9725-e44ba5972b4b', 'cef15d6f-25e4-422b-a7d6-405aaa2de2d5', 'User Delete', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb'),
168-
('d35daa4e-fd02-4934-98d2-5b06e9b694b9', 'c4943131-a642-4352-9725-e44ba5972b4b', '7493f274-5007-4e17-9840-88c9a096422f', 'ActivityLog Read', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb');
171+
('d35daa4e-fd02-4934-98d2-5b06e9b694b9', 'c4943131-a642-4352-9725-e44ba5972b4c', '7493f274-5007-4e17-9840-88c9a096422f', 'ActivityLog Read', NOW(), '26402b6c-ebdd-44c3-9188-659a134819cb');
169172

170173
-- Set up admin role permissions
171174
INSERT INTO "RolePermissions" ("RoleId", "PermissionId", "CreatedOn", "CreatedBy") VALUES

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,27 @@ public async Task<IActionResult> ChangePassword(ChangePassword changePassword)
107107
return Ok(result);
108108
}
109109

110-
[HttpGet("activity-logs")]
110+
[HttpGet("activity-logs")]
111111
[Authorize]
112112
[AuthorizePermission("ActivityLog.Read")]
113-
public async Task<IActionResult> GetActivityLogs([FromQuery] string username, [FromQuery] string email)
113+
public async Task<IActionResult> GetActivityLogs([FromQuery] string username = "", [FromQuery] string email = "")
114114
{
115-
var logs = await _activityLogService.GetActivityLogsAsync(username, email);
116-
return Ok(logs);
115+
// Ensure parameters are not null
116+
username = username ?? "";
117+
email = email ?? "";
118+
119+
// Trim parameters to remove any whitespace
120+
username = username.Trim();
121+
email = email.Trim();
122+
123+
try
124+
{
125+
var logs = await _activityLogService.GetActivityLogsAsync(username, email);
126+
return Ok(logs);
127+
}
128+
catch (Exception ex)
129+
{
130+
return BadRequest(new { message = $"Error retrieving activity logs: {ex.Message}" });
131+
}
117132
}
118133
}

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ public async Task InvokeAsync(HttpContext context)
2727
using (var scope = _serviceProvider.CreateScope())
2828
{
2929
var activityLogService = scope.ServiceProvider.GetRequiredService<IActivityLogService>();
30-
var userIdClaim = context.User.FindFirst("id");
30+
31+
// Safely extract claims from User context
32+
var userIdClaim = context.User.FindFirst("Id");
3133
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;
34+
35+
var username = context.User.Identity?.Name ?? "Anonymous";
36+
37+
// Safely extract email claim
38+
var emailClaim = context.User.FindFirst(System.Security.Claims.ClaimTypes.Email)
39+
?? context.User.FindFirst("Email");
40+
var email = emailClaim?.Value ?? "unknown@example.com";
41+
3442
var activityDescription = activityAttribute.ActivityDescription;
3543
var endpointPath = context.Request.Path;
3644
var httpMethod = context.Request.Method;
37-
var ipAddress = context.Connection.RemoteIpAddress?.ToString();
45+
var ipAddress = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
3846
var userAgent = context.Request.Headers["User-Agent"].ToString();
3947

4048
var logEntry = new ActivityLogEntry

backend/src/Contact.Api/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Contact.Api.Core.Authorization;
22
using Contact.Api.Core.Middleware;
33
using Contact.Application;
4-
using Contact.Application.Interfaces;
54
using Contact.Infrastructure;
65
using Microsoft.AspNetCore.Authentication.JwtBearer;
76
using Microsoft.AspNetCore.Authorization;
@@ -77,6 +76,7 @@
7776
app.UseSwaggerUI();
7877
}
7978
app.UseMiddleware<ExceptionMiddleware>();
79+
app.UseMiddleware<ActivityLoggingMiddleware>();
8080
app.UseHttpsRedirection();
8181

8282
app.UseAuthorization();

backend/src/Contact.Application/ApplicationServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
2626
services.AddScoped<IPermissionService, PermissionService>();
2727
services.AddScoped<IRolePermissionService, RolePermissionService>();
2828
services.AddScoped<IContactPersonService,ContactPersonService>();
29+
services.AddScoped<IActivityLogService,ActivityLogService>();
2930
return services;
3031
}
3132
}

backend/src/Contact.Application/UseCases/Users/RolePermissionResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ public class RolePermissionResponse
55
{
66
public string RoleName { get; set; }
77
public string PageName { get; set; }
8+
public string PageUrl { get; set; }
89
public string OperationName { get; set; }
910
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ public class RolePermissionMapping : BaseEntity
44
{
55
public string RoleName { get; set; }
66
public string PageName { get; set; }
7+
public string PageUrl { get; set; }
78
public string OperationName { get; set; }
89
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public async Task<IEnumerable<ActivityLogEntry>> GetActivityLogsAsync(string use
4242
var sql = @"
4343
SELECT * FROM ""ActivityLog""
4444
WHERE (""Username"" = @Username OR @Username IS NULL)
45-
AND (""Email"" = @Email OR @Email IS NULL)";
45+
OR (""Email"" = @Email OR @Email IS NULL)";
4646

47-
return await _dapperHelper.Query<ActivityLogEntry>(sql, dbPara, CommandType.Text);
47+
return await _dapperHelper.GetAll<ActivityLogEntry>(sql, dbPara, CommandType.Text);
4848
}
4949
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public async Task<IEnumerable<RolePermissionMapping>> GetRolePermissionMappingsA
3939
r.""Name"" AS RoleName,
4040
p.""Id"" as PageId,
4141
p.""Name"" AS PageName,
42-
p.""Url"" as Route,
42+
p.""Url"" as PageUrl,
4343
o.""Id"" as OperationId,
4444
o.""Name"" AS OperationName
4545
FROM ""UserRoles"" ur

frontend/src/app/@core/layout/custom-sidenav/menu-items.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,30 @@ export class MenuService {
3535
return menuItems;
3636
}
3737

38-
// Get unique pages from permissions
39-
const uniquePages = [...new Set(user.rolePermissions.map(p => p.pageName))];
38+
// Create a map to store unique page information
39+
const pageMap = new Map<string, { name: string, url: string }>();
4040

41-
// Create menu items based on permissions
42-
uniquePages.forEach(pageName => {
41+
// Process all role permissions to get unique pages with their URLs
42+
user.rolePermissions.forEach(p => {
43+
if (!pageMap.has(p.pageName)) {
44+
pageMap.set(p.pageName, {
45+
name: p.pageName,
46+
// Use pageUrl if available, fallback to lowercase page name
47+
url: p.pageUrl || `/${p.pageName.toLowerCase()}`
48+
});
49+
}
50+
});
51+
52+
// Create menu items based on pages and permissions
53+
pageMap.forEach((pageInfo, pageName) => {
4354
if (pageName.toLowerCase() === 'dashboard') return;
4455

4556
const operations = this.permissionService.getPagePermissions(pageName);
4657
if (operations.includes('Read')) {
4758
menuItems.push({
4859
icon: this.getIconForPage(pageName),
4960
label: this.formatLabel(pageName),
50-
route: `/${pageName.toLowerCase()}`,
61+
route: pageInfo.url,
5162
exact: false,
5263
permission: `${pageName}.Read`
5364
});

0 commit comments

Comments
 (0)