Something went wrong. Try again.
A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
30 kB · 58 lines
HTML
at gh-pages
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<!doctype html><html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-reference/architecture" data-has-hydrated="false"><head><meta charset="UTF-8"><meta name="generator" content="Docusaurus v3.10.0"><title data-rh="true">Architecture | HappyView</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:image" content="https://happyview.dev/img/og.png"><meta data-rh="true" name="twitter:image" content="https://happyview.dev/img/og.png"><meta data-rh="true" property="og:url" content="https://happyview.dev/reference/architecture"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Architecture | HappyView"><meta data-rh="true" name="description" content="Guide for contributors working on HappyView itself. For a user-facing overview, see the Introduction."><meta data-rh="true" property="og:description" content="Guide for contributors working on HappyView itself. For a user-facing overview, see the Introduction."><link data-rh="true" rel="icon" href="/img/favicon.png"><link data-rh="true" rel="canonical" href="https://happyview.dev/reference/architecture"><link data-rh="true" rel="alternate" href="https://happyview.dev/reference/architecture" hreflang="en"><link data-rh="true" rel="alternate" href="https://happyview.dev/reference/architecture" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Architecture","item":"https://happyview.dev/reference/architecture"}]}</script><link rel="stylesheet" href="/assets/css/styles.18b9c377.css"><script src="/assets/js/runtime~main.413aac68.js" defer="defer"></script><script src="/assets/js/main.c991874c.js" defer="defer"></script></head><body><svg style="display: none;"><defs><symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol></defs></svg><script>!function(){var t=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();document.documentElement.setAttribute("data-theme",t||"light"),document.documentElement.setAttribute("data-theme-choice",t||"light")}(),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><link rel="preload" as="image" href="/img/logo.light.png"><link rel="preload" as="image" href="/img/logo.dark.png"><div role="region" aria-label="Skip to main content"><a class="skipToContent_hn_G" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="/img/logo.light.png" alt="HappyView" class="themedComponent_EfZ_ themedComponent--light_EnZ3"><img src="/img/logo.dark.png" alt="HappyView" class="themedComponent_EfZ_ themedComponent--dark_UTRq"></div><b class="navbar__title text--truncate"></b></a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/">Docs</a></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><a href="https://github.com/gamesgamesgamesgamesgames/happyview" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">GitHub<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_yqny"><use href="#theme-svg-external-link"></use></svg></a><div class="toggle_pDN8 colorModeToggle_WR5U"><button class="clean-btn toggleButton__Co1 toggleButtonDisabled_pTNf" type="button" disabled="" title="system mode" aria-label="Switch between dark and light mode (currently system mode)"><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_IF_c lightToggleIcon_z1QC"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_IF_c darkToggleIcon_O_bk"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_IF_c systemToggleIcon_SKeC"><path fill="currentColor" d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"></path></svg></button></div><div class="navbarSearchContainer_qQFS"></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_NcQk"><div class="docsWrapper_tjyD"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_J6bC" type="button"></button><div class="docRoot_mCS9"><aside class="theme-doc-sidebar-container docSidebarContainer_AIvH"><div class="sidebarViewport_v5_F"><div class="sidebar_TbVu"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_hz51"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/"><span title="Introduction" class="linkLabel_GBZK">Introduction</span></a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/getting-started/quickstart"><span title="Getting Started" class="categoryLinkLabel_ppUb">Getting Started</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/tutorials/statusphere"><span title="Tutorials" class="categoryLinkLabel_ppUb">Tutorials</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/guides/lexicons"><span title="Guides" class="categoryLinkLabel_ppUb">Guides</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/sdk/overview"><span title="JavaScript SDK" class="categoryLinkLabel_ppUb">JavaScript SDK</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" role="button" aria-expanded="true" href="/reference/xrpc-api"><span title="Reference" class="categoryLinkLabel_ppUb">Reference</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/reference/xrpc-api"><span title="XRPC API" class="linkLabel_GBZK">XRPC API</span></a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" tabindex="0" href="/reference/admin-api"><span title="Admin API" class="categoryLinkLabel_ppUb">Admin API</span></a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-2 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="categoryLink_RHuM menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" tabindex="0" href="/reference/lua/record-api"><span title="Lua API" class="categoryLinkLabel_ppUb">Lua API</span></a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/reference/glossary"><span title="Glossary" class="linkLabel_GBZK">Glossary</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/reference/architecture"><span title="Architecture" class="linkLabel_GBZK">Architecture</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/reference/troubleshooting"><span title="Troubleshooting" class="linkLabel_GBZK">Troubleshooting</span></a></li></ul></li></ul></nav></div></div></aside><main class="docMainContainer_Gcuv"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_SLVn"><div class="docItemContainer_gUK8"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer__5rh" aria-label="Breadcrumbs"><ul class="breadcrumbs"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_ilNI"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><span class="breadcrumbs__link">Reference</span></li><li class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link">Architecture</span></li></ul></nav><div class="tocCollapsible_vaUl theme-doc-toc-mobile tocMobile_CcuZ"><button type="button" class="clean-btn tocCollapsibleButton__AHn">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>Architecture</h1></header><p>Guide for contributors working on HappyView itself. For a user-facing overview, see the <a class="" href="/">Introduction</a>.</p><h2 class="anchor anchorTargetStickyNavbar_zGuS" id="system-overview">System overview<a href="#system-overview" class="hash-link" aria-label="Direct link to System overview" title="Direct link to System overview" translate="no"></a></h2><!-- --><p>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 <a href="https://github.com/bluesky-social/jetstream" target="_blank" rel="noopener noreferrer" class="">Jetstream</a>; historical records are backfilled in-process by discovering repos via the relay's <code>listReposByCollection</code> and fetching records directly from each PDS. <a class="" href="/guides/labelers">Labelers</a> are external services that emit content labels over a direct WebSocket connection — they operate out-of-band, outside the relay/repo system.</p><h2 class="anchor anchorTargetStickyNavbar_zGuS" id="request-flow">Request flow<a href="#request-flow" class="hash-link" aria-label="Direct link to Request flow" title="Direct link to Request flow" translate="no"></a></h2><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="reads-queries">Reads (queries)<a href="#reads-queries" class="hash-link" aria-label="Direct link to Reads (queries)" title="Direct link to Reads (queries)" translate="no"></a></h3><!-- --><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="writes-procedures">Writes (procedures)<a href="#writes-procedures" class="hash-link" aria-label="Direct link to Writes (procedures)" title="Direct link to Writes (procedures)" translate="no"></a></h3><!-- --><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="admin-endpoints">Admin endpoints<a href="#admin-endpoints" class="hash-link" aria-label="Direct link to Admin endpoints" title="Direct link to Admin endpoints" translate="no"></a></h3><!-- --><h2 class="anchor anchorTargetStickyNavbar_zGuS" id="data-flow">Data flow<a href="#data-flow" class="hash-link" aria-label="Direct link to Data flow" title="Direct link to Data flow" translate="no"></a></h2><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="real-time-indexing">Real-time indexing<a href="#real-time-indexing" class="hash-link" aria-label="Direct link to Real-time indexing" title="Direct link to Real-time indexing" translate="no"></a></h3><!-- --><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="backfill">Backfill<a href="#backfill" class="hash-link" aria-label="Direct link to Backfill" title="Direct link to Backfill" translate="no"></a></h3><!-- --><h2 class="anchor anchorTargetStickyNavbar_zGuS" id="database-schema">Database schema<a href="#database-schema" class="hash-link" aria-label="Direct link to Database schema" title="Direct link to Database schema" translate="no"></a></h2><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="records"><code>records</code><a href="#records" class="hash-link" aria-label="Direct link to records" title="Direct link to records" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>uri</code></td><td>text (PK)</td><td>AT URI (<code>at://did/collection/rkey</code>)</td></tr><tr><td><code>did</code></td><td>text</td><td>Author DID</td></tr><tr><td><code>collection</code></td><td>text</td><td>Lexicon NSID</td></tr><tr><td><code>rkey</code></td><td>text</td><td>Record key</td></tr><tr><td><code>record</code></td><td>jsonb</td><td>Record value</td></tr><tr><td><code>cid</code></td><td>text</td><td>Content identifier</td></tr><tr><td><code>indexed_at</code></td><td>timestamptz</td><td>When HappyView indexed this record</td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="lexicons"><code>lexicons</code><a href="#lexicons" class="hash-link" aria-label="Direct link to lexicons" title="Direct link to lexicons" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>text (PK)</td><td>Lexicon NSID</td></tr><tr><td><code>revision</code></td><td>integer</td><td>Incremented on upsert</td></tr><tr><td><code>lexicon_json</code></td><td>jsonb</td><td>Raw lexicon definition</td></tr><tr><td><code>lexicon_type</code></td><td>text</td><td>record, query, procedure, definitions</td></tr><tr><td><code>backfill</code></td><td>boolean</td><td>Whether to backfill on upload</td></tr><tr><td><code>target_collection</code></td><td>text</td><td>For queries/procedures: which record collection</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>updated_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="users"><code>users</code><a href="#users" class="hash-link" aria-label="Direct link to users" title="Direct link to users" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>uuid (PK)</td><td></td></tr><tr><td><code>did</code></td><td>text (unique)</td><td>User's atproto DID</td></tr><tr><td><code>is_super</code></td><td>boolean</td><td>Whether this is the super user (only one allowed)</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>last_used_at</code></td><td>timestamptz</td><td>Updated on each authenticated request</td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="user_permissions"><code>user_permissions</code><a href="#user_permissions" class="hash-link" aria-label="Direct link to user_permissions" title="Direct link to user_permissions" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>uuid (FK)</td><td>References <code>users.id</code></td></tr><tr><td><code>permission</code></td><td>text</td><td>Permission string (e.g. <code>lexicons:create</code>)</td></tr><tr><td>(PK)</td><td></td><td>Composite primary key: (<code>user_id</code>, <code>permission</code>)</td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="api_keys"><code>api_keys</code><a href="#api_keys" class="hash-link" aria-label="Direct link to api_keys" title="Direct link to api_keys" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>uuid (PK)</td><td></td></tr><tr><td><code>user_id</code></td><td>uuid (FK)</td><td>References <code>users.id</code></td></tr><tr><td><code>name</code></td><td>text</td><td>Descriptive label</td></tr><tr><td><code>key_hash</code></td><td>text</td><td>SHA-256 hash of the full key</td></tr><tr><td><code>key_prefix</code></td><td>text</td><td>First 11 characters for display</td></tr><tr><td><code>permissions</code></td><td>text[]</td><td>Permissions granted to this key</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>last_used_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>revoked_at</code></td><td>timestamptz</td><td>Set when revoked (soft delete)</td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="oauth_sessions"><code>oauth_sessions</code><a href="#oauth_sessions" class="hash-link" aria-label="Direct link to oauth_sessions" title="Direct link to oauth_sessions" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>did</code></td><td>text (PK)</td><td>User's atproto DID</td></tr><tr><td><code>session_data</code></td><td>text</td><td>Serialized OAuth session (managed by atrium)</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>updated_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="oauth_state"><code>oauth_state</code><a href="#oauth_state" class="hash-link" aria-label="Direct link to oauth_state" title="Direct link to oauth_state" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>state_key</code></td><td>text (PK)</td><td>OAuth state parameter</td></tr><tr><td><code>state_data</code></td><td>text</td><td>Serialized state (managed by atrium)</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="instance_settings"><code>instance_settings</code><a href="#instance_settings" class="hash-link" aria-label="Direct link to instance_settings" title="Direct link to instance_settings" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>key</code></td><td>text (PK)</td><td>Setting name (e.g. <code>app_name</code>)</td></tr><tr><td><code>value</code></td><td>text</td><td>Setting value</td></tr><tr><td><code>updated_at</code></td><td>timestamptz</td><td>Last modified</td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="event_logs"><code>event_logs</code><a href="#event_logs" class="hash-link" aria-label="Direct link to event_logs" title="Direct link to event_logs" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>uuid (PK)</td><td></td></tr><tr><td><code>event_type</code></td><td>text</td><td>Category.action format (e.g. <code>user.created</code>)</td></tr><tr><td><code>severity</code></td><td>text</td><td><code>info</code>, <code>warn</code>, or <code>error</code></td></tr><tr><td><code>actor_did</code></td><td>text</td><td>DID of the user who triggered the event</td></tr><tr><td><code>subject</code></td><td>text</td><td>What was affected (DID, NSID, URI, etc.)</td></tr><tr><td><code>detail</code></td><td>jsonb</td><td>Event-specific data</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="script_variables"><code>script_variables</code><a href="#script_variables" class="hash-link" aria-label="Direct link to script_variables" title="Direct link to script_variables" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>key</code></td><td>text (PK)</td><td>Variable name</td></tr><tr><td><code>value</code></td><td>text</td><td>Variable value (encrypted at rest)</td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>updated_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h3 class="anchor anchorTargetStickyNavbar_zGuS" id="backfill_jobs"><code>backfill_jobs</code><a href="#backfill_jobs" class="hash-link" aria-label="Direct link to backfill_jobs" title="Direct link to backfill_jobs" translate="no"></a></h3><table><thead><tr><th>Column</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>uuid (PK)</td><td></td></tr><tr><td><code>collection</code></td><td>text</td><td>Target collection (null = all)</td></tr><tr><td><code>did</code></td><td>text</td><td>Target DID (null = all)</td></tr><tr><td><code>status</code></td><td>text</td><td>pending, running, completed, failed</td></tr><tr><td><code>total_repos</code></td><td>integer</td><td></td></tr><tr><td><code>processed_repos</code></td><td>integer</td><td></td></tr><tr><td><code>total_records</code></td><td>integer</td><td></td></tr><tr><td><code>error</code></td><td>text</td><td>Error message if failed</td></tr><tr><td><code>started_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>completed_at</code></td><td>timestamptz</td><td></td></tr><tr><td><code>created_at</code></td><td>timestamptz</td><td></td></tr></tbody></table><h2 class="anchor anchorTargetStickyNavbar_zGuS" id="testing">Testing<a href="#testing" class="hash-link" aria-label="Direct link to Testing" title="Direct link to Testing" translate="no"></a></h2><div class="language-sh codeBlockContainer_ei_j theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_p9UO"><pre tabindex="0" class="prism-code language-sh codeBlock_NBAy thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_AQpN"><div class="token-line" style="color:#bfc7d5"><span class="token plain"># Unit tests (no database needed)</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">cargo test --lib</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"># All tests including end-to-end (SQLite by default)</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">cargo test</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"># Or run against Postgres</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">docker compose -f docker-compose.test.yml up -d</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">TEST_DATABASE_URL=postgres://happyview:happyview@localhost:5433/happyview_test cargo test</span><br></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">docker compose -f docker-compose.test.yml down</span><br></div></code></pre></div></div><p>End-to-end tests use <code>wiremock</code> to mock external services (PLC directory, PDSes) and a real database for full integration coverage. By default tests use SQLite; set <code>TEST_DATABASE_URL</code> to a Postgres connection string to test against Postgres.</p></div></article><nav class="docusaurus-mt-lg pagination-nav" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/reference/glossary"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Glossary</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/reference/troubleshooting"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Troubleshooting</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_X45M thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#system-overview" class="table-of-contents__link toc-highlight">System overview</a></li><li><a href="#request-flow" class="table-of-contents__link toc-highlight">Request flow</a><ul><li><a href="#reads-queries" class="table-of-contents__link toc-highlight">Reads (queries)</a></li><li><a href="#writes-procedures" class="table-of-contents__link toc-highlight">Writes (procedures)</a></li><li><a href="#admin-endpoints" class="table-of-contents__link toc-highlight">Admin endpoints</a></li></ul></li><li><a href="#data-flow" class="table-of-contents__link toc-highlight">Data flow</a><ul><li><a href="#real-time-indexing" class="table-of-contents__link toc-highlight">Real-time indexing</a></li><li><a href="#backfill" class="table-of-contents__link toc-highlight">Backfill</a></li></ul></li><li><a href="#database-schema" class="table-of-contents__link toc-highlight">Database schema</a><ul><li><a href="#records" class="table-of-contents__link toc-highlight"><code>records</code></a></li><li><a href="#lexicons" class="table-of-contents__link toc-highlight"><code>lexicons</code></a></li><li><a href="#users" class="table-of-contents__link toc-highlight"><code>users</code></a></li><li><a href="#user_permissions" class="table-of-contents__link toc-highlight"><code>user_permissions</code></a></li><li><a href="#api_keys" class="table-of-contents__link toc-highlight"><code>api_keys</code></a></li><li><a href="#oauth_sessions" class="table-of-contents__link toc-highlight"><code>oauth_sessions</code></a></li><li><a href="#oauth_state" class="table-of-contents__link toc-highlight"><code>oauth_state</code></a></li><li><a href="#instance_settings" class="table-of-contents__link toc-highlight"><code>instance_settings</code></a></li><li><a href="#event_logs" class="table-of-contents__link toc-highlight"><code>event_logs</code></a></li><li><a href="#script_variables" class="table-of-contents__link toc-highlight"><code>script_variables</code></a></li><li><a href="#backfill_jobs" class="table-of-contents__link toc-highlight"><code>backfill_jobs</code></a></li></ul></li><li><a href="#testing" class="table-of-contents__link toc-highlight">Testing</a></li></ul></div></div></div></div></main></div></div></div><footer class="theme-layout-footer footer"><div class="container container-fluid"><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2026 <a href="https://birb.house">Birbhouse Games</a>.</div></div></div></footer></div></body></html>