EduShade
Audit Module

My Activity Logs

Self-service activity log shown to end users in their account settings

My Activity Logs

Every authenticated user can review their own recent activity directly from their account settings. This is the same data stream that admins see in the admin Activity Logs — narrowed at the API level to only the calling user's rows.

Where Users Find It

Logged-in users navigate to:

Account → Settings → Activity Logs

URL: /dashboard/profile/settings/activity-logs

The page renders the UserActivityLogs component (src/components/account/user-activity-logs.tsx) — a card-styled DataTable with row-click detail.

What Users See

The user-facing table is intentionally simpler than the admin view:

ColumnBehavior
DateHuman-formatted timestamp, sortable
ActivityBold title — "User logged in", "Submitted quiz", etc.
ActionCapitalised action code
MethodColoured HTTP method badge (blue/green/yellow/red)
IP AddressMonospace ip_address of the request

Clicking a row opens the same detail drawer that admins see (activity-log-detail.tsx) — title, action, timestamp, request info (method + endpoint), status code, module, IP, user agent, and any metadata JSON.

The user view does not add module/method filter chips. Filtering is intentionally minimal in the user surface; the admin view is where deeper investigation belongs.

API

MethodPathPurpose
GET/v1/user/audit/activity-logsPaginated list, scoped to the caller's user_id
GET/v1/user/audit/activity-logs/:idSingle log by ID, also scoped to the caller

These are the same endpoints documented in Activity Logs — only the scoping differs.

Permission Model

  • No audit.read requirement — any authenticated user may view their own activity.
  • The handler (GetMyActivityLogs / GetMyActivityLog in activity_log_handler.go) extracts the caller's user ID from the JWT and forwards it as a mandatory filter to the service layer.
  • The service layer applies WHERE user_id = ? to both the SELECT and the COUNT — there is no API surface that lets a regular user see anyone else's rows.

What Gets Logged

The same publishing services that feed admin activity logs feed this view too — there is no separate user-only stream. The most common entries a user will see:

SourceExample titles
auth-service"User logged in", "Logged out", "Password changed", "Email verified"
learning-service"Enrolled in course", "Completed lesson"
quiz-service"Submitted quiz", "Started quiz"

Only services that explicitly publish to activity.events produce rows here. The AuditMiddleware writes to the audit stream, not the activity stream — its rows do not show up in this view.

Impersonation actions show up too — see the next section.

Impersonation Visibility

If an admin used Impersonation or Masquerade to act as the user, the resulting row will have a non-empty impersonated_by. The detail drawer surfaces this as "Impersonated By" showing the admin's user ID (UUID).

No name/email enrichment in this view. The user-facing handler (GetMyActivityLogs) deliberately skips the actor lookup that the admin handler performs — so the impersonator (and the user themselves) appears as a raw UUID in the drawer rather than a name + email. The fact that an admin acted is visible; who the admin was can be looked up by an admin via the admin Activity Logs.

This means a user can see whether — and when — an admin acted on their account through the support pathway. There is no way to suppress this from the user-visible log.

Retention

User activity rows follow the same retention policy as admin activity logs — by default 90 days. Older rows are deleted by the scheduled cleanup job. See Retention & Cleanup.

Troubleshooting

IssueSolution
Empty list right after loginThe login event itself is published asynchronously; refresh the page after a couple of seconds
Old activity has disappearedActivity logs are pruned after the retention window (default 90 days) — this is by design
Detail drawer says "Impersonated By: …"An admin acted on your account via the support pathway; the row is the admin audit trail of that session
Cannot open the pageMake sure you are signed in — the route requires a valid JWT

On this page