Skip to content

Audit & GDPR

The audit_logs table is append-only and fire-and-forget — audit failures never block primary operations.

Field Description
actorType teacher, student, system, stripe, admin
actorId Who performed the action
action What happened (e.g., session.create, review.approve)
entityType What was affected (session, student, review, etc.)
entityId Which entity
changes JSONB field-level diff (old → new)
metadata Additional context (JSONB)
ipAddress Client IP
userAgent Client user agent
requestId Correlation ID
// Fire-and-forget — never awaited in the main flow
AuditService.logFromRequest(request, {
action: 'session.create',
entityType: 'session',
entityId: session.id,
changes: AuditService.diff(oldData, newData),
}).catch(() => {});
Category Retention Examples
Financial 7 years Payment, refund, enrollment
Student 3 years Student CRUD, review actions
General 1 year Settings, profile updates
Fallback 6 months Everything else

Monthly cleanup cron job (1st of month, 2 AM) batches deletes per tier.

GET /teacher/audit-log # Paginated logs
GET /teacher/audit-log/entity-history # History for a specific entity
GET /teacher/audit-log/activity-summary # Summary by action type

Students can download all their data:

POST /student/privacy/export

StudentDataExportService.exportStudentData() collects from 14 tables:

  • Student record, enrollments, sessions, session content
  • Reviews, review requests, legal acceptances
  • Contact log, waitlist entries, lifecycle events
  • Notifications, preferred hours, Drive file copies

Students or teachers can request data erasure:

POST /student/privacy/erasure-request
POST /teacher/privacy/erase-student

Erasure process (runs in a single transaction):

  1. DELETE personal data: contact_log, lifecycle_events, legal_acceptances, review_requests, notifications
  2. ANONYMIZE reviews: keep rating/content, remove student identity
  3. ANONYMIZE session content: clear student-specific fields
  4. MARK Drive files as ‘deleted’
  5. ANONYMIZE student record: replace PII, keep ID for FK integrity

data_erasure_requests tracks the request lifecycle:

Status Description
pending Awaiting processing (30-day GDPR due date)
processing Currently being processed
completed Erasure complete, report generated
rejected Request rejected with reason

Report includes detailed JSONB summary of what was erased.

Per-teacher config in data_retention_settings:

Setting Default Description
inactiveStudentDays 730 Days before auto-delete inactive students
sessionContentDays 365 Days to keep session content
contactLogDays 180 Days to keep contact log
autoDeleteEnabled false Enable automatic deletion
notifyBeforeDeleteDays 30 Warning before deletion

Weekly cron job (Sunday 3 AM):

  1. Auto-deletes students inactive past threshold
  2. Cleans old session content and contact log
  3. Checks overdue erasure requests

Event-sourced tracking via student_lifecycle_events (14 event types):

first_contact → trial_requested → trial_completed → first_purchase
→ session_completed → milestone_reached → streak_achieved
→ session_cancelled → no_show → gap_detected
→ enrollment_expiring → churned → reactivated → enrollment_renewed

Daily cron (6 AM) detects:

  • Session gaps: No class in N days
  • Churn: No activity in N days
  • Expiring enrollments: Valid until approaching
  • Session streaks: Consistent weekly sessions

Per-teacher thresholds in retention_settings.

RetentionMetricsService provides:

  • KPIs (churn rate, retention rate, average lifetime)
  • Cohort retention matrix
  • At-risk students list
  • Student journey visualization