EduShade
Audit Module

Audit Module - Overview

Complete guide to the EduShade Audit & Activity logging system

Audit Module

The Audit Module is the platform's tamper-evident record of who did what, when, where, and to which resource. It captures two distinct streams — audit logs for admin/system mutations and activity logs for user actions and HTTP traffic — and exposes them through admin and user-facing UIs.

What This Module Covers

FeatureDescription
Audit LogsAdmin-visible trail of every CRUD mutation across the platform (with before/after snapshots)
Activity LogsAdmin-visible trail of all user activity (logins, enrollments, HTTP calls, impersonation)
My Activity LogsSelf-service activity log shown to end users in their account settings
Event IngestionPub/sub topics, payload contracts, subscribers, and retry semantics
Audit MiddlewareDrop-in Gin middleware that auto-publishes audit events from any service
Filters & SearchAll supported filter dimensions, sorting, and pagination
ExportDownload audit logs as a JSON file with date-range and filter scoping
Retention & CleanupPer-stream retention policy, scheduled cleanup, and the system cleanup endpoint

Architecture Overview

The audit system is an independent microservice (audit-service) that consumes events from a pub/sub bus and serves a small set of read APIs. No application code writes audit logs directly — every record arrives through the event bus.

Key Components

  • Backend: Go (Gin framework) with Bun ORM on PostgreSQL
  • Frontend: Next.js admin pages + an embedded user-account widget (/admin/audit-logs, /admin/activity-logs, /dashboard/profile/settings/activity-logs)
  • Transport: Redis-backed pub/sub (Watermill) on the topics audit.events and activity.events
  • Storage: Two PostgreSQL tables — audit_logs and activity_logs — in the dedicated edushade_audit database
  • Multi-Tenancy: Tenant scoping is enforced on every read query; every audit row carries a tenant_id

Service Topology

The audit-service connects to three PostgreSQL databases:

DatabasePurpose
edushade_auditOwns the audit_logs and activity_logs tables
edushade_auth (RBAC DB)Read-only — used for permission checks and to enrich logs with actor user details
edushade_tenantRead-only — used by the tenant resolver middleware

Default service port: 8096.

Two Streams: Audit vs Activity

EduShade keeps two distinct event streams. They look similar but answer different questions.

StreamAnswersCaptured byStored in
Audit logs"Who changed what, and what did the data look like before and after?"Mutation handlers + the AuditMiddleware on admin route groupsaudit_logs
Activity logs"What did users do — login, enrol, submit a quiz, hit this endpoint?"Service handlers (auth-service, learning-service, etc.) publishing on user actionsactivity_logs

A useful rule of thumb:

If the row exists to prove an admin did something, it's an audit log. If the row exists to show what a user did, it's an activity log.

Both streams support impersonation (the activity log additionally carries an impersonated_by column).

How a Log Gets There (High-Level Flow)

1. Some service does a mutation OR a user action happens
2. Service builds an AuditEventPayload or ActivityEventPayload
3. Service publishes it to "audit.events" or "activity.events" via Redis pub/sub
4. audit-service subscriber picks the message up
5. Subscriber unmarshals, validates, persists to PostgreSQL
6. Admin or user reads it back via REST endpoints (with tenant scoping)
7. Retention scheduler periodically deletes rows older than the cutoff

The end-to-end path is fully asynchronous — publishing services do not wait for the audit-service to acknowledge. This means a slow or briefly-down audit-service does not block user-facing requests; missed events are recovered from Redis on next consumption (with bounded retry).

Multi-Tenancy

Tenant isolation is non-negotiable:

  • Every audit_logs row has a tenant_id (NOT NULL).
  • activity_logs rows have a nullable tenant_id so genuinely cross-tenant system events can be recorded — but read APIs always filter by the caller's tenant.
  • The composite index (tenant_id, created_at DESC) exists on both tables — pagination and date-range queries are tenant-rooted by design.
  • Cross-tenant reads are not exposed by any endpoint.

Getting Started

If you're an administrator, start with:

  1. Audit Logs — Browse the admin mutation trail
  2. Activity Logs — Browse user action and HTTP request history
  3. Filters & Search — Narrow results by action, module, date range, and more
  4. Export — Pull a JSON snapshot for offline review

If you're a service author wiring a new microservice into the audit stream, read:

  1. Event Ingestion — The payload shapes you must publish
  2. Audit Middleware — The drop-in Gin middleware that does most of the work for you

If you're an operator, also see:

  1. Retention & Cleanup — Schedule, batch sizes, and manual triggers
  2. Event Ingestion — Topic names, retry semantics, and how to wire a new publisher

On this page