Skip to main content

Architecture

Guide for contributors working on HappyView itself. For a user-facing overview, see the Introduction.

System overview

Queries go through the query handler to the database (SQLite by default, or Postgres). Writes go through the procedure handler to the user's PDS, then HappyView indexes the record locally. Real-time record events stream in via Jetstream; historical records are backfilled in-process by discovering repos via the relay's listReposByCollection and fetching records directly from each PDS. Labelers are external services that emit content labels over a direct WebSocket connection — they operate out-of-band, outside the relay/repo system.

Request flow

Reads (queries)

Writes (procedures)

Admin endpoints

Data flow

Real-time indexing

Backfill

Database schema

records

ColumnTypeDescription
uritext (PK)AT URI (at://did/collection/rkey)
didtextAuthor DID
collectiontextLexicon NSID
rkeytextRecord key
recordjsonbRecord value
cidtextContent identifier
indexed_attimestamptzWhen HappyView indexed this record

lexicons

ColumnTypeDescription
idtext (PK)Lexicon NSID
revisionintegerIncremented on upsert
lexicon_jsonjsonbRaw lexicon definition
lexicon_typetextrecord, query, procedure, definitions
backfillbooleanWhether to backfill on upload
target_collectiontextFor queries/procedures: which record collection
created_attimestamptz
updated_attimestamptz

users

ColumnTypeDescription
iduuid (PK)
didtext (unique)User's atproto DID
is_superbooleanWhether this is the super user (only one allowed)
created_attimestamptz
last_used_attimestamptzUpdated on each authenticated request

user_permissions

ColumnTypeDescription
user_iduuid (FK)References users.id
permissiontextPermission string (e.g. lexicons:create)
(PK)Composite primary key: (user_id, permission)

api_keys

ColumnTypeDescription
iduuid (PK)
user_iduuid (FK)References users.id
nametextDescriptive label
key_hashtextSHA-256 hash of the full key
key_prefixtextFirst 11 characters for display
permissionstext[]Permissions granted to this key
created_attimestamptz
last_used_attimestamptz
revoked_attimestamptzSet when revoked (soft delete)

oauth_sessions

ColumnTypeDescription
didtext (PK)User's atproto DID
session_datatextSerialized OAuth session (managed by atrium)
created_attimestamptz
updated_attimestamptz

oauth_state

ColumnTypeDescription
state_keytext (PK)OAuth state parameter
state_datatextSerialized state (managed by atrium)
created_attimestamptz

instance_settings

ColumnTypeDescription
keytext (PK)Setting name (e.g. app_name)
valuetextSetting value
updated_attimestamptzLast modified

event_logs

ColumnTypeDescription
iduuid (PK)
event_typetextCategory.action format (e.g. user.created)
severitytextinfo, warn, or error
actor_didtextDID of the user who triggered the event
subjecttextWhat was affected (DID, NSID, URI, etc.)
detailjsonbEvent-specific data
created_attimestamptz

script_variables

ColumnTypeDescription
keytext (PK)Variable name
valuetextVariable value (encrypted at rest)
created_attimestamptz
updated_attimestamptz

backfill_jobs

ColumnTypeDescription
iduuid (PK)
collectiontextTarget collection (null = all)
didtextTarget DID (null = all)
statustextpending, running, completed, failed
total_reposinteger
processed_reposinteger
total_recordsinteger
errortextError message if failed
started_attimestamptz
completed_attimestamptz
created_attimestamptz

Testing

# Unit tests (no database needed)
cargo test --lib

# All tests including end-to-end (SQLite by default)
cargo test

# Or run against Postgres
docker compose -f docker-compose.test.yml up -d
TEST_DATABASE_URL=postgres://happyview:happyview@localhost:5433/happyview_test cargo test
docker compose -f docker-compose.test.yml down

End-to-end tests use wiremock to mock external services (PLC directory, PDSes) and a real database for full integration coverage. By default tests use SQLite; set TEST_DATABASE_URL to a Postgres connection string to test against Postgres.