Action for Happiness
Log in

← All schema domains · View source on GitHub →

User

A person with an account, and the records that track what they do across the platform — the action log and the champion pipeline.

See also: course (answers, enrollments), cohort, theme (forum moderation), nudge (delivery logs).

Tables

USER

A person with an account — one row per signed-in human. The roles array gates capabilities; a user can hold any combination of student, teacher, admin, champion, superadmin. Almost everything else in the schema either belongs to, targets, or is performed by a USER.

id           usr_42_maya
email        maya.chen@example.com
name         Maya Chen
origin       signup-form
profile_img  https://cdn.afh.example/u/42.jpg
roles        ["student", "champion"]
created_at   2026-03-04T14:22:00Z

USER_ACTION

Append-only event log of things users do across the platform. action_type is one of enrolled, viewed_module, started_module, completed_module, answered_question, posted_thread, posted_reply, completed_course, unenrolled. target_type + target_id form a polymorphic pointer at what they acted on (module, content_block, question, course, cohort, forum, thread, post); metadata JSONB carries action-specific detail. Powers analytics, progress tracking, and event-triggered nudges (see nudge).

The three source-tracking columns make ingestion idempotent — so the same upstream event recorded twice (a webhook retry, a replayed Segment event) only lands once:

  • source_system — where the event came from: webhook, app, stripe, segment, manual, cron_worker.
  • source_event_id — the upstream event's own identifier, when it has one.
  • idempotency_key — a deterministic key the application computes to prevent recording the same source event twice.

Two partial unique indexes enforce this, scoped so they only apply when the relevant columns are non-null (NULLs stay unconstrained, since not every action has an upstream source):

CREATE UNIQUE INDEX user_action_source_event_unique
  ON user_action (source_system, source_event_id)
  WHERE source_system IS NOT NULL
    AND source_event_id IS NOT NULL;

CREATE UNIQUE INDEX user_action_idempotency_key_unique
  ON user_action (idempotency_key)
  WHERE idempotency_key IS NOT NULL;
id               act_2026_03_15_001
user_id          usr_42_maya
enrollment_id    enr_01_april_maya
action_type      viewed_module
target_type      module
target_id        mod_01_day1
metadata         {"duration_sec": 92, "device": "ios"}
source_system    segment
source_event_id  evt_seg_8f2a91
idempotency_key  user_action:segment:evt_seg_8f2a91
occurred_at      2026-03-15T08:14:00Z

CHAMPION

Tracks the champion pipeline — users being considered as, or actively serving as, community champions. stage walks candidateselectedchampionalumni; cohort_id is nullable, where NULL means an ongoing/global champion not tied to a specific cohort run.

id                  ch_07_maya
user_id             usr_42_maya
cohort_id           null
stage               champion
notes               Strong forum presence; volunteered to mentor.
selected_at         2026-04-01T10:00:00Z
became_champion_at  2026-04-20T10:00:00Z