Skip to content

Audit & GDPR

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

FieldDescription
actorTypeteacher, student, system, stripe, admin
actorIdWho performed the action
actionWhat happened (e.g., session.create, review.approve)
entityTypeWhat was affected (session, student, review, etc.)
entityIdWhich entity
changesJSONB field-level diff (old → new)
metadataAdditional context (JSONB)
ipAddressClient IP
userAgentClient user agent
requestIdCorrelation 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(() => {});
CategoryRetentionExamples
Financial7 yearsPayment, refund, enrollment
Student3 yearsStudent CRUD, review actions
General1 yearSettings, profile updates
Fallback6 monthsEverything 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:

StatusDescription
pendingAwaiting processing (30-day GDPR due date)
processingCurrently being processed
completedErasure complete, report generated
rejectedRequest rejected with reason

Report includes detailed JSONB summary of what was erased.

Per-teacher config in data_retention_settings:

SettingDefaultDescription
inactiveStudentDays730Days before auto-delete inactive students
sessionContentDays365Days to keep session content
contactLogDays180Days to keep contact log
autoDeleteEnabledfalseEnable automatic deletion
notifyBeforeDeleteDays30Warning 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