5838 lines
112 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
<meta name="author" content="Bunker Operations">
<link rel="canonical" href="https://bnkserve.org/v2/database/">
<link rel="prev" href="../frontend/pages/volunteer/my-routes-page/">
<link rel="next" href="schema/">
<link rel="icon" href="../../assets/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
<title>Database Documentation - Changemaker Lite</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="../../stylesheets/extra.css">
<link rel="stylesheet" href="../../stylesheets/home.css">
<link rel="stylesheet" href="../../assets/css/video-player.css">
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
<meta property="og:type" content="website" />
<meta property="og:title" content="Database Documentation - Changemaker Lite" />
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="og:image" content="https://bnkserve.org/assets/images/social/v2/database/index.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://bnkserve.org/v2/database/" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="Database Documentation - Changemaker Lite" />
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/v2/database/index.png" />
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#database-documentation" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../assets/logo.png" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Changemaker Lite
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Database Documentation
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
</nav>
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../" class="md-tabs__link">
V2 Documentation
</a>
</li>
<li class="md-tabs__item">
<a href="../../phil/" class="md-tabs__link">
Philosophy
</a>
</li>
<li class="md-tabs__item">
<a href="../../v1/" class="md-tabs__link">
V1 Documentation (Legacy)
</a>
</li>
<li class="md-tabs__item">
<a href="../../blog/" class="md-tabs__link">
Blog
</a>
</li>
</ul>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
<img src="../../assets/logo.png" alt="logo">
</a>
Changemaker Lite
</label>
<div class="md-nav__source">
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
changemaker.lite
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
<label class="md-nav__link " for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
V2 Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_2" >
<div class="md-nav__link md-nav__container">
<a href="../getting-started/" class="md-nav__link ">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<label class="md-nav__link " for="__nav_2_2" id="__nav_2_2_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../getting-started/quick-start/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_3" >
<div class="md-nav__link md-nav__container">
<a href="../architecture/" class="md-nav__link ">
<span class="md-ellipsis">
Architecture
</span>
</a>
<label class="md-nav__link " for="__nav_2_3" id="__nav_2_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_3">
<span class="md-nav__icon md-icon"></span>
Architecture
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../architecture/dual-api/" class="md-nav__link">
<span class="md-ellipsis">
Dual API System
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../architecture/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication & Security
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_4" >
<div class="md-nav__link md-nav__container">
<a href="../backend/" class="md-nav__link ">
<span class="md-ellipsis">
Backend
</span>
</a>
<label class="md-nav__link " for="__nav_2_4" id="__nav_2_4_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_4">
<span class="md-nav__icon md-icon"></span>
Backend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../backend/modules/" class="md-nav__link">
<span class="md-ellipsis">
Modules
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../backend/services/" class="md-nav__link">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../backend/middleware/" class="md-nav__link">
<span class="md-ellipsis">
Middleware
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../backend/utilities/" class="md-nav__link">
<span class="md-ellipsis">
Utilities
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_5" >
<div class="md-nav__link md-nav__container">
<a href="../frontend/" class="md-nav__link ">
<span class="md-ellipsis">
Frontend
</span>
</a>
<label class="md-nav__link " for="__nav_2_5" id="__nav_2_5_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_5">
<span class="md-nav__icon md-icon"></span>
Frontend
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../frontend/components/" class="md-nav__link">
<span class="md-ellipsis">
Components
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../frontend/layouts/" class="md-nav__link">
<span class="md-ellipsis">
Layouts
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../frontend/pages/" class="md-nav__link">
<span class="md-ellipsis">
Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_6" checked>
<div class="md-nav__link md-nav__container">
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Database
</span>
</a>
<label class="md-nav__link md-nav__link--active" for="__nav_2_6" id="__nav_2_6_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_6_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_6">
<span class="md-nav__icon md-icon"></span>
Database
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="schema/" class="md-nav__link">
<span class="md-ellipsis">
Schema Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="seeding/" class="md-nav__link">
<span class="md-ellipsis">
Seeding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="indexes/" class="md-nav__link">
<span class="md-ellipsis">
Indexes
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="models/" class="md-nav__link">
<span class="md-ellipsis">
Models
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_7" >
<div class="md-nav__link md-nav__container">
<a href="../features/" class="md-nav__link ">
<span class="md-ellipsis">
Features
</span>
</a>
<label class="md-nav__link " for="__nav_2_7" id="__nav_2_7_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_7_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_7">
<span class="md-nav__icon md-icon"></span>
Features
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/influence/" class="md-nav__link">
<span class="md-ellipsis">
Influence
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/map/" class="md-nav__link">
<span class="md-ellipsis">
Map
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/landing-pages/" class="md-nav__link">
<span class="md-ellipsis">
Landing Pages
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/email-templates/" class="md-nav__link">
<span class="md-ellipsis">
Email Templates
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/media/" class="md-nav__link">
<span class="md-ellipsis">
Media
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/newsletter/" class="md-nav__link">
<span class="md-ellipsis">
Newsletter
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/observability/" class="md-nav__link">
<span class="md-ellipsis">
Observability
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../features/tunnel/" class="md-nav__link">
<span class="md-ellipsis">
Tunnel
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_8" >
<div class="md-nav__link md-nav__container">
<a href="../deployment/" class="md-nav__link ">
<span class="md-ellipsis">
Deployment
</span>
</a>
<label class="md-nav__link " for="__nav_2_8" id="__nav_2_8_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_8">
<span class="md-nav__icon md-icon"></span>
Deployment
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../deployment/docker-compose/" class="md-nav__link">
<span class="md-ellipsis">
Docker Compose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/environment-variables/" class="md-nav__link">
<span class="md-ellipsis">
Environment Variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/nginx/" class="md-nav__link">
<span class="md-ellipsis">
Nginx Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/ssl-tls/" class="md-nav__link">
<span class="md-ellipsis">
SSL/TLS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/tunneling/" class="md-nav__link">
<span class="md-ellipsis">
Tunneling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/monitoring-stack/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Stack
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/healthchecks/" class="md-nav__link">
<span class="md-ellipsis">
Health Checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/scaling/" class="md-nav__link">
<span class="md-ellipsis">
Scaling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../deployment/backup-restore/" class="md-nav__link">
<span class="md-ellipsis">
Backup & Restore
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_9" >
<div class="md-nav__link md-nav__container">
<a href="../development/" class="md-nav__link ">
<span class="md-ellipsis">
Development
</span>
</a>
<label class="md-nav__link " for="__nav_2_9" id="__nav_2_9_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_9">
<span class="md-nav__icon md-icon"></span>
Development
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../development/local-setup/" class="md-nav__link">
<span class="md-ellipsis">
Local Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/docker-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Docker Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/git-workflow/" class="md-nav__link">
<span class="md-ellipsis">
Git Workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/npm-commands/" class="md-nav__link">
<span class="md-ellipsis">
NPM Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/migrations/" class="md-nav__link">
<span class="md-ellipsis">
Migrations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/typescript/" class="md-nav__link">
<span class="md-ellipsis">
TypeScript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/debugging/" class="md-nav__link">
<span class="md-ellipsis">
Debugging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../development/code-style/" class="md-nav__link">
<span class="md-ellipsis">
Code Style
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_10" >
<div class="md-nav__link md-nav__container">
<a href="../api-reference/" class="md-nav__link ">
<span class="md-ellipsis">
API Reference
</span>
</a>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_10_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_10">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_11" >
<div class="md-nav__link md-nav__container">
<a href="../user-guides/" class="md-nav__link ">
<span class="md-ellipsis">
User Guides
</span>
</a>
<label class="md-nav__link " for="__nav_2_11" id="__nav_2_11_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_11_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_11">
<span class="md-nav__icon md-icon"></span>
User Guides
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../user-guides/admin-guide/" class="md-nav__link">
<span class="md-ellipsis">
Admin Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guides/campaign-manager-guide/" class="md-nav__link">
<span class="md-ellipsis">
Campaign Manager Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guides/map-organizer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Map Organizer Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guides/content-editor-guide/" class="md-nav__link">
<span class="md-ellipsis">
Content Editor Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../user-guides/volunteer-guide/" class="md-nav__link">
<span class="md-ellipsis">
Volunteer Guide
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_12" >
<div class="md-nav__link md-nav__container">
<a href="../troubleshooting/" class="md-nav__link ">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
<label class="md-nav__link " for="__nav_2_12" id="__nav_2_12_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_12_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_12">
<span class="md-nav__icon md-icon"></span>
Troubleshooting
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../troubleshooting/faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/common-errors/" class="md-nav__link">
<span class="md-ellipsis">
Common Errors
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/auth-issues/" class="md-nav__link">
<span class="md-ellipsis">
Auth Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/database-issues/" class="md-nav__link">
<span class="md-ellipsis">
Database Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/docker-issues/" class="md-nav__link">
<span class="md-ellipsis">
Docker Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/email-issues/" class="md-nav__link">
<span class="md-ellipsis">
Email Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/geocoding-issues/" class="md-nav__link">
<span class="md-ellipsis">
Geocoding Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/monitoring-issues/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Issues
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/performance-optimization/" class="md-nav__link">
<span class="md-ellipsis">
Performance Optimization
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_13" >
<div class="md-nav__link md-nav__container">
<a href="../migration/" class="md-nav__link ">
<span class="md-ellipsis">
Migration
</span>
</a>
<label class="md-nav__link " for="__nav_2_13" id="__nav_2_13_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_13_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_13">
<span class="md-nav__icon md-icon"></span>
Migration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../migration/feature-parity/" class="md-nav__link">
<span class="md-ellipsis">
Feature Parity
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../migration/breaking-changes/" class="md-nav__link">
<span class="md-ellipsis">
Breaking Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../migration/api-changes/" class="md-nav__link">
<span class="md-ellipsis">
API Changes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../migration/data-migration/" class="md-nav__link">
<span class="md-ellipsis">
Data Migration
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2_14" >
<div class="md-nav__link md-nav__container">
<a href="../contributing/" class="md-nav__link ">
<span class="md-ellipsis">
Contributing
</span>
</a>
<label class="md-nav__link " for="__nav_2_14" id="__nav_2_14_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_14_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_14">
<span class="md-nav__icon md-icon"></span>
Contributing
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../contributing/development-setup/" class="md-nav__link">
<span class="md-ellipsis">
Development Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../contributing/code-of-conduct/" class="md-nav__link">
<span class="md-ellipsis">
Code of Conduct
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../contributing/pull-requests/" class="md-nav__link">
<span class="md-ellipsis">
Pull Requests
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../contributing/roadmap/" class="md-nav__link">
<span class="md-ellipsis">
Roadmap
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../phil/" class="md-nav__link">
<span class="md-ellipsis">
Philosophy
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../v1/" class="md-nav__link">
<span class="md-ellipsis">
V1 Documentation (Legacy)
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
<a href="../../blog/" class="md-nav__link">
<span class="md-ellipsis">
Blog
</span>
<span class="md-nav__icon md-icon"></span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="On this page">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
On this page
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
<nav class="md-nav" aria-label="Overview">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#database-architecture" class="md-nav__link">
<span class="md-ellipsis">
Database Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#key-design-patterns" class="md-nav__link">
<span class="md-ellipsis">
Key Design Patterns
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#complete-entity-relationship-diagram" class="md-nav__link">
<span class="md-ellipsis">
Complete Entity Relationship Diagram
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#model-groups" class="md-nav__link">
<span class="md-ellipsis">
Model Groups
</span>
</a>
<nav class="md-nav" aria-label="Model Groups">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-auth-users" class="md-nav__link">
<span class="md-ellipsis">
1. Auth &amp; Users
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#2-influence" class="md-nav__link">
<span class="md-ellipsis">
2. Influence
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#3-map-locations" class="md-nav__link">
<span class="md-ellipsis">
3. Map — Locations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#4-canvassing" class="md-nav__link">
<span class="md-ellipsis">
4. Canvassing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#5-email-templates" class="md-nav__link">
<span class="md-ellipsis">
5. Email Templates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#6-landing-pages" class="md-nav__link">
<span class="md-ellipsis">
6. Landing Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#7-settings" class="md-nav__link">
<span class="md-ellipsis">
7. Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#8-media-drizzle-orm" class="md-nav__link">
<span class="md-ellipsis">
8. Media (Drizzle ORM)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#9-sharedstandalone-models" class="md-nav__link">
<span class="md-ellipsis">
9. Shared/Standalone Models
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#field-types-reference" class="md-nav__link">
<span class="md-ellipsis">
Field Types Reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#enum-definitions" class="md-nav__link">
<span class="md-ellipsis">
Enum Definitions
</span>
</a>
<nav class="md-nav" aria-label="Enum Definitions">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#auth-users" class="md-nav__link">
<span class="md-ellipsis">
Auth &amp; Users
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#influence" class="md-nav__link">
<span class="md-ellipsis">
Influence
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#map" class="md-nav__link">
<span class="md-ellipsis">
Map
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#canvassing" class="md-nav__link">
<span class="md-ellipsis">
Canvassing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#email-templates" class="md-nav__link">
<span class="md-ellipsis">
Email Templates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#landing-pages" class="md-nav__link">
<span class="md-ellipsis">
Landing Pages
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#media-drizzle" class="md-nav__link">
<span class="md-ellipsis">
Media (Drizzle)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#index-strategy-overview" class="md-nav__link">
<span class="md-ellipsis">
Index Strategy Overview
</span>
</a>
<nav class="md-nav" aria-label="Index Strategy Overview">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#foreign-key-indexes" class="md-nav__link">
<span class="md-ellipsis">
Foreign Key Indexes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#composite-indexes" class="md-nav__link">
<span class="md-ellipsis">
Composite Indexes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#unique-constraints" class="md-nav__link">
<span class="md-ellipsis">
Unique Constraints
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#foreign-key-conventions" class="md-nav__link">
<span class="md-ellipsis">
Foreign Key Conventions
</span>
</a>
<nav class="md-nav" aria-label="Foreign Key Conventions">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#cascade-deletes" class="md-nav__link">
<span class="md-ellipsis">
Cascade Deletes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#set-null" class="md-nav__link">
<span class="md-ellipsis">
Set Null
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#related-documentation" class="md-nav__link">
<span class="md-ellipsis">
Related Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#quick-links" class="md-nav__link">
<span class="md-ellipsis">
Quick Links
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<nav class="md-path" aria-label="Navigation" >
<ol class="md-path__list">
<li class="md-path__item">
<a href="../.." class="md-path__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-path__item">
<a href="../" class="md-path__link">
<span class="md-ellipsis">
V2 Documentation
</span>
</a>
</li>
<li class="md-path__item">
<a href="./" class="md-path__link">
<span class="md-ellipsis">
Database
</span>
</a>
</li>
</ol>
</nav>
<article class="md-content__inner md-typeset">
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/database/index.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/v2/database/index.md" title="View source of this page" class="md-content__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
</a>
<h1 id="database-documentation">Database Documentation<a class="headerlink" href="#database-documentation" title="Permanent link">&para;</a></h1>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>Changemaker Lite V2 uses a <strong>dual ORM architecture</strong> with PostgreSQL 16 as the backing database:</p>
<ul>
<li><strong>Prisma ORM</strong> (Express API, port 4000) — 30 models for auth, influence, map, canvassing, email templates, landing pages, and tracking</li>
<li><strong>Drizzle ORM</strong> (Fastify Media API, port 4100) — 3 models for video library, compilations, and job queue</li>
</ul>
<p>Both ORMs share the same PostgreSQL database but maintain separate schemas and migration workflows.</p>
<h3 id="database-architecture">Database Architecture<a class="headerlink" href="#database-architecture" title="Permanent link">&para;</a></h3>
<p><strong>Database:</strong> PostgreSQL 16
<strong>Connection:</strong> <code>DATABASE_URL</code> environment variable
<strong>Total Models:</strong> 33 models organized into 9 groups
<strong>Migration Tools:</strong> Prisma Migrate (main API), Drizzle Kit (media API)</p>
<h3 id="key-design-patterns">Key Design Patterns<a class="headerlink" href="#key-design-patterns" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>Audit Fields</strong> — Most models include:</li>
<li><code>createdAt</code> / <code>updatedAt</code> timestamps</li>
<li><code>createdByUserId</code> / <code>updatedByUserId</code> user references</li>
<li>
<p>Automatic tracking via Prisma middleware</p>
</li>
<li>
<p><strong>Soft Deletes</strong> — Some models use status fields instead of hard deletes:</p>
</li>
<li>User: <code>status</code> (ACTIVE/INACTIVE/SUSPENDED/EXPIRED)</li>
<li>Campaign: <code>status</code> (DRAFT/ACTIVE/PAUSED/ARCHIVED)</li>
<li>
<p>Shift: <code>status</code> (OPEN/FULL/CANCELLED)</p>
</li>
<li>
<p><strong>JSON Fields</strong> — Used for flexible schema:</p>
</li>
<li><code>permissions</code> (User) — granular per-app permissions</li>
<li><code>offices</code> (Representative) — array of office contact info</li>
<li><code>tags</code> (videos) — array of tag strings</li>
<li><code>geojson</code> (Cut) — GeoJSON polygon coordinates</li>
<li>
<p><code>blocks</code> (LandingPage) — GrapesJS editor output</p>
</li>
<li>
<p><strong>Enums</strong> — 18 enums for type safety:</p>
</li>
<li>
<p>UserRole, UserStatus, CampaignStatus, GovernmentLevel, EmailMethod, ResponseType, ResponseStatus, SupportLevel, GeocodeProvider, BuildingType, LocationHistoryAction, ShiftStatus, SignupStatus, SignupSource, CutCategory, VisitOutcome, CanvassSessionStatus, TrackPointEvent, EmailTemplateCategory, EditorMode, MkdocsExportMode</p>
</li>
<li>
<p><strong>Cascade Deletes</strong> — Foreign keys with <code>onDelete: Cascade</code>:</p>
</li>
<li>Deleting a Campaign deletes all CampaignEmail, RepresentativeResponse, CustomRecipient, Call records</li>
<li>Deleting a Location deletes all Address and LocationHistory records</li>
<li>Deleting a Shift deletes all ShiftSignup records</li>
<li>
<p>Deleting a CanvassSession deletes all CanvassVisit records</p>
</li>
<li>
<p><strong>Indexes</strong> — Strategic indexing for performance:</p>
</li>
<li>All foreign keys indexed (userId, campaignId, locationId, etc.)</li>
<li>Composite indexes for common queries (latitude+longitude, locationId+unitNumber, etc.)</li>
<li>Unique constraints (email, slug, postalCode, token, etc.)</li>
</ol>
<h2 id="complete-entity-relationship-diagram">Complete Entity Relationship Diagram<a class="headerlink" href="#complete-entity-relationship-diagram" title="Permanent link">&para;</a></h2>
<pre class="mermaid"><code>erDiagram
%% ============================================================================
%% AUTH &amp; USERS
%% ============================================================================
User ||--o{ RefreshToken : has
User ||--o{ Campaign : creates
User ||--o{ CampaignEmail : sends
User ||--o{ RepresentativeResponse : submits
User ||--o{ ResponseUpvote : upvotes
User ||--o{ ShiftSignup : "signs up for"
User ||--o{ Location : creates
User ||--o{ Location : updates
User ||--o{ Address : "creates (addresses)"
User ||--o{ Address : "updates (addresses)"
User ||--o{ LocationHistory : edits
User ||--o{ Cut : "creates (cuts)"
User ||--o{ CanvassVisit : visits
User ||--o{ CanvassSession : "has (sessions)"
User ||--o{ TrackingSession : "tracks (gps)"
User ||--o{ EmailTemplate : "creates (templates)"
User ||--o{ EmailTemplate : "updates (templates)"
User ||--o{ EmailTemplateVersion : "versions (templates)"
User ||--o{ EmailTemplateTestLog : "tests (templates)"
User {
String id PK
String email UK "bcrypt hashed"
String password "bcrypt"
String name
String phone
UserRole role "SUPER_ADMIN | INFLUENCE_ADMIN | MAP_ADMIN | USER | TEMP"
UserStatus status "ACTIVE | INACTIVE | SUSPENDED | EXPIRED"
Json permissions "granular per-app"
UserCreatedVia createdVia "ADMIN | PUBLIC_SHIFT_SIGNUP | STANDARD"
DateTime expiresAt "for TEMP users"
Int expireDays
DateTime lastLoginAt
Boolean emailVerified
DateTime createdAt
DateTime updatedAt
}
RefreshToken {
String id PK
String token UK "JWT refresh token"
String userId FK
DateTime expiresAt
DateTime createdAt
}
%% ============================================================================
%% INFLUENCE — CAMPAIGNS
%% ============================================================================
Campaign ||--o{ CampaignEmail : sends
Campaign ||--o{ RepresentativeResponse : receives
Campaign ||--o{ CustomRecipient : targets
Campaign ||--o{ Call : tracks
Campaign {
String id PK
String slug UK
String title
String description
String emailSubject
String emailBody
String callToAction
String coverPhoto
CampaignStatus status "DRAFT | ACTIVE | PAUSED | ARCHIVED"
Boolean allowSmtpEmail "default: true"
Boolean allowMailtoLink "default: true"
Boolean collectUserInfo "default: true"
Boolean showEmailCount "default: true"
Boolean showCallCount "default: true"
Boolean allowEmailEditing "default: false"
Boolean allowCustomRecipients "default: false"
Boolean showResponseWall "default: false"
Boolean highlightCampaign "default: false"
GovernmentLevel[] targetGovernmentLevels
String createdByUserId FK
String createdByUserEmail
String createdByUserName
DateTime createdAt
DateTime updatedAt
}
CampaignEmail {
String id PK
String campaignId FK
String campaignSlug
String userId FK
String userEmail
String userName
String userPostalCode
String recipientEmail
String recipientName
String recipientTitle
GovernmentLevel recipientLevel
EmailMethod emailMethod "SMTP | MAILTO"
String subject
String message
CampaignEmailStatus status "QUEUED | SENT | FAILED | CLICKED | USER_INFO_CAPTURED"
String senderIp
DateTime sentAt
}
Representative {
String id PK
String postalCode IDX
String name
String email
String districtName
String electedOffice
String partyName
String representativeSetName
String url
String photoUrl
Json offices "array of office contact info"
DateTime cachedAt
}
CustomRecipient {
String id PK
String campaignId FK
String campaignSlug
String recipientName
String recipientEmail
String recipientTitle
String recipientOrganization
String notes
Boolean isActive
DateTime createdAt
DateTime updatedAt
}
PostalCodeCache {
String id PK
String postalCode UK
String city
String province
Decimal centroidLat
Decimal centroidLng
DateTime lastUpdated
}
Call {
String id PK
String representativeName
String representativeTitle
String phoneNumber
String officeType
String callerName
String callerEmail
String postalCode
String campaignId FK
String campaignSlug
String callerIp
DateTime calledAt
}
%% ============================================================================
%% INFLUENCE — RESPONSE WALL
%% ============================================================================
RepresentativeResponse ||--o{ ResponseUpvote : gets
RepresentativeResponse {
String id PK
String campaignId FK
String campaignSlug
String representativeName
String representativeTitle
GovernmentLevel representativeLevel
String representativeEmail
ResponseType responseType "EMAIL | LETTER | PHONE_CALL | MEETING | SOCIAL_MEDIA | OTHER"
String responseText
String userComment
String screenshotUrl
String submittedByUserId FK
String submittedByName
String submittedByEmail
Boolean isAnonymous
ResponseStatus status "PENDING | APPROVED | REJECTED"
Boolean isVerified
String verificationToken
DateTime verificationSentAt
DateTime verifiedAt
String verifiedBy
Int upvoteCount
String submittedIp
DateTime createdAt
DateTime updatedAt
}
ResponseUpvote {
String id PK
String responseId FK
String userId FK
String userEmail
String upvotedIp
}
EmailLog {
String id PK
String recipientEmail
String senderName
String senderEmail
String subject
String message
String postalCode
String status "sent | failed | previewed"
String senderIp
DateTime sentAt
}
EmailVerification {
String id PK
String token UK
String email
String tempCampaignData "JSON"
DateTime createdAt
DateTime expiresAt
Boolean used
}
%% ============================================================================
%% MAP — LOCATIONS
%% ============================================================================
Location ||--o{ Address : contains
Location ||--o{ LocationHistory : logs
Location {
String id PK
Decimal latitude "required, precision: 10,8"
Decimal longitude "required, precision: 11,8"
String address "base street address, no unit"
String postalCode
String province
String federalDistrict
Int buildingUse "NAR BU_USE: 1=Residential, 2=Partial, 3=Non-Residential, 4=Unknown"
String locGuid UK "NAR LOC_GUID"
BuildingType buildingType "SINGLE_FAMILY | MULTI_UNIT | MIXED_USE | COMMERCIAL"
Int totalUnits
String buildingNotes "access codes, manager contact"
Int geocodeConfidence "0-100"
GeocodeProvider geocodeProvider
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
Address {
String id PK
String locationId FK
String unitNumber
String addrGuid UK "NAR ADDR_GUID"
String firstName
String lastName
String email
String phone
SupportLevel supportLevel "1 | 2 | 3 | 4"
Boolean sign
String signSize
String notes
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
LocationHistory {
String id PK
String locationId FK
String userId FK
LocationHistoryAction action "CREATED | UPDATED | GEOCODED | BULK_GEOCODED | MOVED_ON_MAP | IMPORTED_CSV | IMPORTED_NAR"
String field "which field changed"
String oldValue
String newValue
Json metadata "provider, confidence, etc"
DateTime createdAt
}
%% ============================================================================
%% MAP — SHIFTS &amp; CUTS
%% ============================================================================
Cut ||--o{ Shift : schedules
Shift ||--o{ ShiftSignup : has
Shift ||--o{ CanvassVisit : "visits (shift)"
Shift ||--o{ CanvassSession : "sessions (shift)"
Shift {
String id PK
String title
String description
DateTime date
String startTime "HH:MM"
String endTime "HH:MM"
String location
Int maxVolunteers
Int currentVolunteers
ShiftStatus status "OPEN | FULL | CANCELLED"
Boolean isPublic
String cutId FK
String createdBy
DateTime createdAt
DateTime updatedAt
}
ShiftSignup {
String id PK
String shiftId FK
String shiftTitle
String userId FK
String userEmail
String userName
String userPhone
DateTime signupDate
SignupStatus status "CONFIRMED | CANCELLED"
SignupSource signupSource "AUTHENTICATED | PUBLIC | ADMIN"
}
Cut {
String id PK
String name
String description
String color
Decimal opacity
CutCategory category "CUSTOM | WARD | NEIGHBORHOOD | DISTRICT"
Boolean isPublic
Boolean isOfficial
String geojson "GeoJSON polygon data"
String bounds "bounding box JSON"
Boolean showLocations
Boolean exportEnabled
String assignedTo
Json filterSettings
DateTime lastCanvassed
Int completionPercentage
String createdByUserId FK
DateTime createdAt
DateTime updatedAt
}
MapSettings {
String id PK
Decimal latitude
Decimal longitude
Int zoom
String walkSheetTitle
String walkSheetSubtitle
String walkSheetFooter
String qrCode1Url
String qrCode1Label
String qrCode2Url
String qrCode2Label
String qrCode3Url
String qrCode3Label
String createdBy
DateTime createdAt
DateTime updatedAt
}
%% ============================================================================
%% CANVASSING
%% ============================================================================
Cut ||--o{ CanvassSession : "sessions (cut)"
CanvassSession ||--o{ CanvassVisit : records
CanvassSession ||--|| TrackingSession : tracks
Address ||--o{ CanvassVisit : "visited (address)"
CanvassSession {
String id PK
String userId FK
String cutId FK
String shiftId FK
CanvassSessionStatus status "ACTIVE | COMPLETED | ABANDONED"
DateTime startedAt
DateTime endedAt
Decimal startLatitude
Decimal startLongitude
}
CanvassVisit {
String id PK
String addressId FK
String userId FK
String shiftId FK
String sessionId FK
VisitOutcome outcome "NOT_HOME | REFUSED | MOVED | ALREADY_VOTED | SPOKE_WITH | LEFT_LITERATURE | COME_BACK_LATER"
SupportLevel supportLevel
Boolean signRequested
String signSize
String notes
Int durationSeconds
DateTime visitedAt
}
TrackingSession {
String id PK
String userId FK
String canvassSessionId UK
DateTime startedAt
DateTime endedAt
Boolean isActive
Int totalPoints
Float totalDistanceM
Decimal lastLatitude
Decimal lastLongitude
DateTime lastRecordedAt
}
TrackingSession ||--o{ TrackPoint : logs
TrackPoint {
String id PK
String trackingSessionId FK
Decimal latitude
Decimal longitude
Float accuracy
DateTime recordedAt
TrackPointEvent eventType "LOCATION_ADDED | VISIT_RECORDED | SESSION_STARTED | SESSION_ENDED"
}
%% ============================================================================
%% EMAIL TEMPLATES
%% ============================================================================
EmailTemplate ||--o{ EmailTemplateVariable : defines
EmailTemplate ||--o{ EmailTemplateVersion : versions
EmailTemplate ||--o{ EmailTemplateTestLog : tests
EmailTemplate {
String id PK
String key UK "e.g., campaign-email"
String name "display name"
String description
EmailTemplateCategory category "INFLUENCE | MAP | SYSTEM"
String subjectLine "with {{VAR}} support"
String htmlContent
String textContent
Boolean isSystem "prevent deletion"
Boolean isActive
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
EmailTemplateVariable {
String id PK
String templateId FK
String key "e.g., USER_NAME"
String label "e.g., User Name"
String description
Boolean isRequired
Boolean isConditional "used in {{#if}} blocks"
String sampleValue
Int sortOrder
}
EmailTemplateVersion {
String id PK
String templateId FK
Int versionNumber "auto-increment per template"
String subjectLine
String htmlContent
String textContent
String changeNotes
String createdByUserId FK
DateTime createdAt
}
EmailTemplateTestLog {
String id PK
String templateId FK
String recipientEmail
Json testData "sample variable values"
Boolean success
String errorMessage
String messageId "nodemailer message ID"
String sentByUserId FK
DateTime sentAt
}
%% ============================================================================
%% LANDING PAGES
%% ============================================================================
LandingPage {
String id PK
String slug UK
String title
String description
Json blocks "GrapesJS editor JSON"
String htmlOutput
String cssOutput
EditorMode editorMode "VISUAL | CODE"
String mkdocsPath "path in mkdocs/overrides/"
String mkdocsStubPath "path to .md stub"
MkdocsExportMode mkdocsExportMode "THEMED | STANDALONE"
Boolean mkdocsHideNav
Boolean mkdocsHideToc
Boolean mkdocsSkipExport
Boolean published
String seoTitle
String seoDescription
String seoImage
DateTime createdAt
DateTime updatedAt
}
PageBlock {
String id PK
String type "hero | text | image | cta | features | testimonials | form"
String label
Json schema "block configuration schema"
Json defaults "default values"
String thumbnail
String category
Int sortOrder
DateTime createdAt
DateTime updatedAt
}
%% ============================================================================
%% SITE SETTINGS
%% ============================================================================
SiteSettings {
String id PK
String organizationName
String organizationShortName
String organizationLogoUrl
String organizationFaviconUrl
String adminColorPrimary
String adminColorBgBase
String publicColorPrimary
String publicColorBgBase
String publicColorBgContainer
String publicHeaderGradient
String footerText
String loginSubtitle
String emailFromName
String smtpHost
Int smtpPort
String smtpUser
String smtpPass
String smtpFromAddress
String smtpActiveProvider "mailhog | production"
Boolean emailTestMode
String testEmailRecipient
Boolean enableInfluence
Boolean enableMap
Boolean enableNewsletter
Boolean enableLandingPages
DateTime createdAt
DateTime updatedAt
}</code></pre>
<h2 id="model-groups">Model Groups<a class="headerlink" href="#model-groups" title="Permanent link">&para;</a></h2>
<p>The database is organized into 9 logical groups:</p>
<h3 id="1-auth-users">1. <a href="models/auth/">Auth &amp; Users</a><a class="headerlink" href="#1-auth-users" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>User</strong> — User accounts with roles (SUPER_ADMIN, INFLUENCE_ADMIN, MAP_ADMIN, USER, TEMP)</li>
<li><strong>RefreshToken</strong> — JWT refresh token storage with rotation</li>
</ul>
<p><strong>Key Features:</strong> bcrypt passwords (12+ chars policy), role-based access control, temp user expiration, email verification</p>
<h3 id="2-influence">2. <a href="models/influence/">Influence</a><a class="headerlink" href="#2-influence" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Campaign</strong> — Advocacy campaigns with 12 feature flags</li>
<li><strong>Representative</strong> — Cached representative data from Represent API</li>
<li><strong>CampaignEmail</strong> — Email tracking (SMTP vs MAILTO)</li>
<li><strong>RepresentativeResponse</strong> — Response wall with moderation</li>
<li><strong>ResponseUpvote</strong> — Upvote tracking with IP + user uniqueness</li>
<li><strong>CustomRecipient</strong> — Custom email targets</li>
<li><strong>PostalCodeCache</strong> — Postal code geocoding cache</li>
<li><strong>EmailLog</strong> — Email audit trail</li>
<li><strong>EmailVerification</strong> — Verification token storage</li>
<li><strong>Call</strong> — Phone call tracking</li>
</ul>
<p><strong>Key Features:</strong> Multi-government-level targeting, response moderation workflow (PENDING → APPROVED/REJECTED), BullMQ integration for email queue, upvote deduplication</p>
<h3 id="3-map-locations">3. <a href="models/map/">Map — Locations</a><a class="headerlink" href="#3-map-locations" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Location</strong> — Building-level data with lat/lng, NAR integration</li>
<li><strong>Address</strong> — Unit-level data with support levels</li>
<li><strong>LocationHistory</strong> — Audit trail with 7 action types</li>
<li><strong>Shift</strong> — Volunteer shifts with cut relation</li>
<li><strong>ShiftSignup</strong> — Signup tracking</li>
<li><strong>Cut</strong> — GeoJSON polygon overlays</li>
<li><strong>MapSettings</strong> — Singleton for map center/zoom + walk sheet config</li>
</ul>
<p><strong>Key Features:</strong> Building vs unit architecture, multi-provider geocoding (6 providers), NAR 2025 import support, spatial indexing, GeoJSON storage</p>
<h3 id="4-canvassing">4. <a href="models/canvass/">Canvassing</a><a class="headerlink" href="#4-canvassing" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>CanvassSession</strong> — Session lifecycle (ACTIVE → COMPLETED/ABANDONED)</li>
<li><strong>CanvassVisit</strong> — Visit recording with 7 outcome types</li>
<li><strong>TrackingSession</strong> — GPS tracking integration</li>
<li><strong>TrackPoint</strong> — GPS breadcrumb trail</li>
</ul>
<p><strong>Key Features:</strong> Walking route algorithm, session abandonment logic (12h timeout), distance calculation, support level tracking</p>
<h3 id="5-email-templates">5. <a href="models/email-templates/">Email Templates</a><a class="headerlink" href="#5-email-templates" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>EmailTemplate</strong> — Template master with categories</li>
<li><strong>EmailTemplateVariable</strong> — Variable definitions with validation</li>
<li><strong>EmailTemplateVersion</strong> — Version history</li>
<li><strong>EmailTemplateTestLog</strong> — Test email audit</li>
</ul>
<p><strong>Key Features:</strong> Handlebars-style variable interpolation ({{VAR}}), conditional variables, system template protection, version auto-increment</p>
<h3 id="6-landing-pages">6. <a href="models/pages/">Landing Pages</a><a class="headerlink" href="#6-landing-pages" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>LandingPage</strong> — GrapesJS editor output with MkDocs export</li>
<li><strong>PageBlock</strong> — Reusable block library</li>
</ul>
<p><strong>Key Features:</strong> GrapesJS JSON storage, MkDocs export modes (THEMED vs STANDALONE), SEO metadata, slug-based routing</p>
<h3 id="7-settings">7. <a href="models/settings/">Settings</a><a class="headerlink" href="#7-settings" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>SiteSettings</strong> — Org branding + theme + SMTP + feature toggles</li>
<li><strong>MapSettings</strong> — Map center/zoom + walk sheet config</li>
</ul>
<p><strong>Key Features:</strong> Singleton pattern, SMTP override hierarchy (SiteSettings → .env), feature flags</p>
<h3 id="8-media-drizzle-orm">8. <a href="models/media/">Media (Drizzle ORM)</a><a class="headerlink" href="#8-media-drizzle-orm" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>videos</strong> — Video library with metadata, directory types, engagement stats</li>
<li><strong>compilations</strong> — Video compilation tracking</li>
<li><strong>jobs</strong> — Job queue with resource categories</li>
</ul>
<p><strong>Key Features:</strong> Dual ORM architecture, FFprobe metadata extraction, directory type enum (9 types), job queue with GPU/CPU resource tracking</p>
<h3 id="9-sharedstandalone-models">9. <a href="#">Shared/Standalone Models</a><a class="headerlink" href="#9-sharedstandalone-models" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Representative</strong> — Shared across campaigns</li>
<li><strong>PostalCodeCache</strong> — Shared geocoding cache</li>
<li><strong>EmailLog</strong> — Audit trail (no relations)</li>
<li><strong>EmailVerification</strong> — Standalone verification tokens</li>
</ul>
<h2 id="field-types-reference">Field Types Reference<a class="headerlink" href="#field-types-reference" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Prisma Type</th>
<th>PostgreSQL Type</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>String</code></td>
<td><code>text</code></td>
<td>Variable-length text</td>
<td><code>"admin@cmlite.org"</code></td>
</tr>
<tr>
<td><code>String @db.Text</code></td>
<td><code>text</code></td>
<td>Long-form text (no char limit)</td>
<td>Campaign descriptions</td>
</tr>
<tr>
<td><code>Int</code></td>
<td><code>integer</code></td>
<td>32-bit integer</td>
<td><code>42</code></td>
</tr>
<tr>
<td><code>BigInt</code></td>
<td><code>bigint</code></td>
<td>64-bit integer (Node: <code>number</code> mode)</td>
<td>File sizes</td>
</tr>
<tr>
<td><code>Boolean</code></td>
<td><code>boolean</code></td>
<td>True/false</td>
<td><code>true</code></td>
</tr>
<tr>
<td><code>Decimal</code></td>
<td><code>numeric</code></td>
<td>Arbitrary precision decimal</td>
<td>Lat/lng coordinates</td>
</tr>
<tr>
<td><code>Decimal @db.Decimal(10, 8)</code></td>
<td><code>numeric(10, 8)</code></td>
<td>10 digits, 8 after decimal</td>
<td><code>53.54612345</code></td>
</tr>
<tr>
<td><code>DateTime</code></td>
<td><code>timestamp with time zone</code></td>
<td>Timestamp</td>
<td><code>2025-02-11T10:30:00Z</code></td>
</tr>
<tr>
<td><code>DateTime @db.Date</code></td>
<td><code>date</code></td>
<td>Date only (no time)</td>
<td>Shift dates</td>
</tr>
<tr>
<td><code>Json</code></td>
<td><code>jsonb</code></td>
<td>JSON data (binary storage)</td>
<td>Arrays, objects</td>
</tr>
<tr>
<td><code>Enum</code></td>
<td><code>enum</code></td>
<td>Enumerated type</td>
<td><code>UserRole.SUPER_ADMIN</code></td>
</tr>
</tbody>
</table>
<h2 id="enum-definitions">Enum Definitions<a class="headerlink" href="#enum-definitions" title="Permanent link">&para;</a></h2>
<h3 id="auth-users">Auth &amp; Users<a class="headerlink" href="#auth-users" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>UserRole:</strong> <code>SUPER_ADMIN</code>, <code>INFLUENCE_ADMIN</code>, <code>MAP_ADMIN</code>, <code>USER</code>, <code>TEMP</code></li>
<li><strong>UserStatus:</strong> <code>ACTIVE</code>, <code>INACTIVE</code>, <code>SUSPENDED</code>, <code>EXPIRED</code></li>
<li><strong>UserCreatedVia:</strong> <code>ADMIN</code>, <code>PUBLIC_SHIFT_SIGNUP</code>, <code>STANDARD</code></li>
</ul>
<h3 id="influence">Influence<a class="headerlink" href="#influence" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>CampaignStatus:</strong> <code>DRAFT</code>, <code>ACTIVE</code>, <code>PAUSED</code>, <code>ARCHIVED</code></li>
<li><strong>GovernmentLevel:</strong> <code>FEDERAL</code>, <code>PROVINCIAL</code>, <code>MUNICIPAL</code>, <code>SCHOOL_BOARD</code></li>
<li><strong>EmailMethod:</strong> <code>SMTP</code>, <code>MAILTO</code></li>
<li><strong>CampaignEmailStatus:</strong> <code>QUEUED</code>, <code>SENT</code>, <code>FAILED</code>, <code>CLICKED</code>, <code>USER_INFO_CAPTURED</code></li>
<li><strong>ResponseType:</strong> <code>EMAIL</code>, <code>LETTER</code>, <code>PHONE_CALL</code>, <code>MEETING</code>, <code>SOCIAL_MEDIA</code>, <code>OTHER</code></li>
<li><strong>ResponseStatus:</strong> <code>PENDING</code>, <code>APPROVED</code>, <code>REJECTED</code></li>
</ul>
<h3 id="map">Map<a class="headerlink" href="#map" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>SupportLevel:</strong> <code>LEVEL_1</code> (mapped to <code>"1"</code>), <code>LEVEL_2</code>, <code>LEVEL_3</code>, <code>LEVEL_4</code></li>
<li><strong>GeocodeProvider:</strong> <code>GOOGLE</code>, <code>MAPBOX</code>, <code>NOMINATIM</code>, <code>PHOTON</code>, <code>LOCATIONIQ</code>, <code>ARCGIS</code>, <code>UNKNOWN</code></li>
<li><strong>BuildingType:</strong> <code>SINGLE_FAMILY</code>, <code>MULTI_UNIT</code>, <code>MIXED_USE</code>, <code>COMMERCIAL</code></li>
<li><strong>LocationHistoryAction:</strong> <code>CREATED</code>, <code>UPDATED</code>, <code>GEOCODED</code>, <code>BULK_GEOCODED</code>, <code>MOVED_ON_MAP</code>, <code>IMPORTED_CSV</code>, <code>IMPORTED_NAR</code></li>
<li><strong>ShiftStatus:</strong> <code>OPEN</code>, <code>FULL</code>, <code>CANCELLED</code></li>
<li><strong>SignupStatus:</strong> <code>CONFIRMED</code>, <code>CANCELLED</code></li>
<li><strong>SignupSource:</strong> <code>AUTHENTICATED</code>, <code>PUBLIC</code>, <code>ADMIN</code></li>
<li><strong>CutCategory:</strong> <code>CUSTOM</code>, <code>WARD</code>, <code>NEIGHBORHOOD</code>, <code>DISTRICT</code></li>
</ul>
<h3 id="canvassing">Canvassing<a class="headerlink" href="#canvassing" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>VisitOutcome:</strong> <code>NOT_HOME</code>, <code>REFUSED</code>, <code>MOVED</code>, <code>ALREADY_VOTED</code>, <code>SPOKE_WITH</code>, <code>LEFT_LITERATURE</code>, <code>COME_BACK_LATER</code></li>
<li><strong>CanvassSessionStatus:</strong> <code>ACTIVE</code>, <code>COMPLETED</code>, <code>ABANDONED</code></li>
<li><strong>TrackPointEvent:</strong> <code>LOCATION_ADDED</code>, <code>VISIT_RECORDED</code>, <code>SESSION_STARTED</code>, <code>SESSION_ENDED</code></li>
</ul>
<h3 id="email-templates">Email Templates<a class="headerlink" href="#email-templates" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>EmailTemplateCategory:</strong> <code>INFLUENCE</code>, <code>MAP</code>, <code>SYSTEM</code></li>
</ul>
<h3 id="landing-pages">Landing Pages<a class="headerlink" href="#landing-pages" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>EditorMode:</strong> <code>VISUAL</code>, <code>CODE</code></li>
<li><strong>MkdocsExportMode:</strong> <code>THEMED</code>, <code>STANDALONE</code></li>
</ul>
<h3 id="media-drizzle">Media (Drizzle)<a class="headerlink" href="#media-drizzle" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>DirectoryType</strong> (TypeScript literal): <code>'studios'</code>, <code>'gifs'</code>, <code>'private'</code>, <code>'inbox'</code>, <code>'curated'</code>, <code>'playback'</code>, <code>'compilations'</code>, <code>'videos'</code>, <code>'highlights'</code></li>
<li><strong>ResourceCategory</strong> (TypeScript literal): <code>'gpu_ai'</code>, <code>'gpu_encode'</code>, <code>'cpu'</code></li>
<li><strong>JobStatus</strong> (TypeScript literal): <code>'pending'</code>, <code>'queued'</code>, <code>'running'</code>, <code>'completed'</code>, <code>'failed'</code>, <code>'cancelled'</code></li>
</ul>
<h2 id="index-strategy-overview">Index Strategy Overview<a class="headerlink" href="#index-strategy-overview" title="Permanent link">&para;</a></h2>
<h3 id="foreign-key-indexes">Foreign Key Indexes<a class="headerlink" href="#foreign-key-indexes" title="Permanent link">&para;</a></h3>
<p>All foreign key fields are indexed for join performance:
- <code>userId</code>, <code>campaignId</code>, <code>locationId</code>, <code>addressId</code>, <code>shiftId</code>, <code>cutId</code>, <code>sessionId</code>, <code>templateId</code>, <code>trackingSessionId</code></p>
<h3 id="composite-indexes">Composite Indexes<a class="headerlink" href="#composite-indexes" title="Permanent link">&para;</a></h3>
<p>Strategic multi-column indexes for common query patterns:
- <code>[latitude, longitude]</code> (Location) — spatial queries
- <code>[locationId, unitNumber]</code> (Address) — unit lookups
- <code>[campaignId, status]</code> (RepresentativeResponse) — filtered response lists
- <code>[isActive, lastRecordedAt]</code> (TrackingSession) — active session cleanup
- <code>[templateId, createdAt(sort: Desc)]</code> (EmailTemplateVersion) — version history
- <code>[directoryType, isValid, orientation]</code> (videos) — media library filtering</p>
<h3 id="unique-constraints">Unique Constraints<a class="headerlink" href="#unique-constraints" title="Permanent link">&para;</a></h3>
<p>Enforce data integrity:
- <code>email</code> (User)
- <code>slug</code> (Campaign, LandingPage)
- <code>postalCode</code> (PostalCodeCache)
- <code>token</code> (RefreshToken, EmailVerification)
- <code>key</code> (EmailTemplate)
- <code>[responseId, userId]</code> (ResponseUpvote) — prevent duplicate upvotes from logged-in users
- <code>[responseId, upvotedIp]</code> (ResponseUpvote) — prevent duplicate upvotes from same IP
- <code>[shiftId, userEmail]</code> (ShiftSignup) — prevent duplicate shift signups
- <code>[templateId, key]</code> (EmailTemplateVariable) — unique variable keys per template
- <code>[templateId, versionNumber]</code> (EmailTemplateVersion) — sequential version numbers</p>
<h2 id="foreign-key-conventions">Foreign Key Conventions<a class="headerlink" href="#foreign-key-conventions" title="Permanent link">&para;</a></h2>
<h3 id="cascade-deletes">Cascade Deletes<a class="headerlink" href="#cascade-deletes" title="Permanent link">&para;</a></h3>
<p><div class="language-text highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>onDelete: Cascade
</span></code></pre></div>
Used when child records should be deleted with parent:
- RefreshToken → User
- CampaignEmail → Campaign
- RepresentativeResponse → Campaign
- CustomRecipient → Campaign
- Call → Campaign (SetNull)
- Address → Location
- LocationHistory → Location
- ShiftSignup → Shift
- CanvassVisit → Address, CanvassSession
- TrackPoint → TrackingSession
- EmailTemplateVariable → EmailTemplate
- EmailTemplateVersion → EmailTemplate
- EmailTemplateTestLog → EmailTemplate</p>
<h3 id="set-null">Set Null<a class="headerlink" href="#set-null" title="Permanent link">&para;</a></h3>
<p><div class="language-text highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a>onDelete: SetNull
</span></code></pre></div>
Used when child records should remain but orphan the reference:
- Campaign.createdByUserId → User
- CampaignEmail.userId → User
- RepresentativeResponse.submittedByUserId → User
- Location.createdByUserId/updatedByUserId → User
- Shift.cutId → Cut
- CanvassSession.shiftId → Shift
- TrackingSession.canvassSessionId → CanvassSession</p>
<h2 id="related-documentation">Related Documentation<a class="headerlink" href="#related-documentation" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="schema/">Schema Reference</a> — Complete table and field listing</li>
<li><a href="migrations/">Migration Workflow</a> — Prisma and Drizzle migration processes</li>
<li><a href="seeding/">Seeding</a> — Default data and seed script</li>
<li><a href="indexes/">Indexes</a> — Detailed index strategy and performance notes</li>
<li><a href="models/auth/">Auth Models</a> — User and authentication tables</li>
<li><a href="models/influence/">Influence Models</a> — Campaign and advocacy tables</li>
<li><a href="models/map/">Map Models</a> — Location, shift, and cut tables</li>
<li><a href="models/canvass/">Canvassing Models</a> — Session and visit tracking</li>
<li><a href="models/email-templates/">Email Template Models</a> — Template system</li>
<li><a href="models/pages/">Landing Page Models</a> — Page builder and blocks</li>
<li><a href="models/settings/">Settings Models</a> — Site and map settings</li>
<li><a href="models/media/">Media Models</a> — Video library (Drizzle ORM)</li>
</ul>
<h2 id="quick-links">Quick Links<a class="headerlink" href="#quick-links" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="https://github.com/changemaker-lite/api/blob/v2/prisma/schema.prisma">Prisma Schema File</a></li>
<li><a href="https://github.com/changemaker-lite/api/blob/v2/src/modules/media/db/schema.ts">Drizzle Schema File</a></li>
<li><a href="../api/index.md">API Documentation</a></li>
<li><a href="../admin/index.md">Admin GUI Documentation</a></li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../frontend/pages/volunteer/my-routes-page/" class="md-footer__link md-footer__link--prev" aria-label="Previous: My Routes">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
My Routes
</div>
</div>
</a>
<a href="schema/" class="md-footer__link md-footer__link--next" aria-label="Next: Schema Overview">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Schema Overview
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2024 The Bunker Operations <a href="#__consent">Change cookie settings</a>
</div>
</div>
<div class="md-social">
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.79ae519e.min.js"></script>
<script src="../../javascripts/home.js"></script>
<script src="../../javascripts/github-widget.js"></script>
<script src="../../javascripts/gitea-widget.js"></script>
<script src="../../assets/js/env-config.js"></script>
<script src="../../assets/js/video-player.js"></script>
</body>
</html>