Add video card insert feature + MkDocs video hydration + fixes

- New video card block for GrapesJS landing pages, email templates,
  MkDocs export, and documentation editor Insert dropdown
- Shared HTML generators in admin/src/utils/videoCardHtml.ts
- MkDocs video-player.js hydrates .video-card-block elements:
  thumbnail fix via MEDIA_API_URL, click-to-play inline, Gallery link
- Media API CORS: auto-add MkDocs + docs subdomain origins
- env_config_hook.py: smart Docker hostname detection, ADMIN_PORT
  resolution, pass env vars to MkDocs container
- Gallery URL uses /gallery?expanded=ID format
- VideoPickerModal: fix double /api prefix and Docker hostname thumbs
- Seed: default-video-card PageBlock
- Remove V1 legacy code (influence/, map/)

Bunker Admin
This commit is contained in:
2026-02-17 15:42:32 -07:00
parent 58dc1942ec
commit 99a6abab06
1511 changed files with 14551 additions and 1625410 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -8,7 +8,7 @@
"assets/images/social/blog/2025/08/01/3.png": "332b80224e75bda48c92439ad6354e7ffcab52e1",
"assets/images/social/blog/2025/09/24/4.png": "840234a707ac182ce6b89203c658b312d03df58e",
"assets/images/social/blog/archive/2025.png": "08cbed159d450158ab4d79807f37adcda08bce39",
"assets/images/social/blog/index.png": "7a5388a2a7e752cce942be0455d9fbf7fa816d12",
"assets/images/social/blog/index.png": "b6fe388cb026572dfa0f42b9a6833e9fa9f95b9e",
"assets/images/social/build/index.png": "30e75040da55694ca6485d51a35fb4d19a26b408",
"assets/images/social/build/influence.png": "2961db1abb5f36d53086bf2bde9dfe19f1487809",
"assets/images/social/build/map.png": "ef5bf7b7e7c3e0e527d66b3d3292918de78b7198",
@@ -19,6 +19,17 @@
"assets/images/social/config/index.png": "1903ccb7f8af2454c97b8a70059119c1527826fb",
"assets/images/social/config/map.png": "322276b5f574c461e2cafc4c5c8e8735bed2d25e",
"assets/images/social/config/mkdocs.png": "b23831844fca01c49624fa378a20453daae66604",
"assets/images/social/docs/admin/index.png": "fca983b7d9dc1cbac915d8369f2d6354e3902fa5",
"assets/images/social/docs/api/index.png": "6d589b655cd1681d187337b6c46275d9c4758361",
"assets/images/social/docs/architecture/index.png": "480bf36086518125519527af26a31c3be8b1d04c",
"assets/images/social/docs/deployment/index.png": "2ef61cacc08a8a0f6bc94308936ec7731fe5938a",
"assets/images/social/docs/features/index.png": "f092b70692e778c4f570fb7942113ceb36f5110c",
"assets/images/social/docs/getting-started/environment-variables.png": "a2ac6ca4cb56f9697fc7fd25f9cca6f1aa83a58b",
"assets/images/social/docs/getting-started/index.png": "c38af587f205180bab6232e517438de6db89fc88",
"assets/images/social/docs/index.png": "473afed8e6ed44768b1a64ad90c4a8595667a8f3",
"assets/images/social/docs/services/index.png": "9fcf00324266a9f7b58c7b277da2127e8882aa47",
"assets/images/social/docs/troubleshooting/index.png": "b0ffadb8b01b261dfe7dea1b55fe02a3d639b7e7",
"assets/images/social/docs/volunteer/index.png": "a38ac1baf53fc19a77832926d14d56b92bbc5662",
"assets/images/social/how%20to/canvass.png": "83854d6765f418b3cd83651cd8828034d5a5027d",
"assets/images/social/index.png": "e78b3d8cfb2c7529a587def60aaa7e158e0fb176",
"assets/images/social/lander.png": "6ca837e423f4f2e6f786243bddefc3e55ced0818",
@@ -40,7 +51,7 @@
"assets/images/social/services/postgresql.png": "831fb68dd3e01d9a017e59b100aaa8a455c8c112",
"assets/images/social/services/static-server.png": "f36d527c80adba4bcb7778784683f429acb4ce74",
"assets/images/social/test-2.png": "a6ae43d52d7c58fc106a562777e03b7da2263f83",
"assets/images/social/test.png": "18d63169d742e6321dc4bb2988b8c5de61e79a28",
"assets/images/social/test.png": "a6ae43d52d7c58fc106a562777e03b7da2263f83",
"assets/images/social/v1/adv/ansible.png": "cb542ad9a3cc9a869258b3b1353966e1b9616a2b",
"assets/images/social/v1/adv/index.png": "faa3ec092003114c031995ba6258c4d43f4262a4",
"assets/images/social/v1/adv/vscode-ssh.png": "7c88c30c6bfb74736a308407d1a3b7cf3381d42b",

View File

@@ -1,431 +0,0 @@
# Changemaker Lite V2 - Admin Documentation Validation Report
**Report Date:** 2026-02-12
**Documentation Path:** `/home/bunker-admin/changemaker.lite/mkdocs/docs/v2/frontend/pages/admin/`
**Validator:** Claude Sonnet 4.5
---
## Executive Summary
### Strengths
-**29 out of 30** expected admin pages documented
-**28,457 total lines** of comprehensive documentation
-**97% structural compliance** (28/29 files have all required sections)
-**Excellent code example coverage** (average 59 code blocks per file)
-**Consistent formatting** and structure across all files
-**Strong API integration documentation** (average 6 endpoints per file)
### Areas for Improvement
- ⚠️ **1 missing file:** page-editor-page.md
- ⚠️ **14 files** have broken cross-reference links
- ⚠️ **6 service pages** lack response examples for API endpoints
- ⚠️ **1 file** (email-queue-page.md) has untagged code blocks
- ⚠️ **1 file** (dashboard-page.md) missing Troubleshooting section
### Critical Issues
-**None** (all critical sections present in 96.5% of files)
---
## 1. File Existence Analysis
**Total Files Expected:** 30
**Total Files Found:** 29
**Missing Files:** 1
### Documented Files (29)
- users-page.md
- dashboard-page.md
- settings-page.md
- campaigns-page.md
- responses-page.md
- representatives-page.md
- email-queue-page.md
- locations-page.md
- cuts-page.md
- map-settings-page.md
- shifts-page.md
- canvass-dashboard-page.md
- landing-pages-page.md
- email-templates-page.md
- email-template-editor-page.md
- listmonk-page.md
- pangolin-page.md
- docs-page.md
- observability-page.md
- data-quality-dashboard-page.md
- mkdocs-settings-page.md
- mini-qr-page.md
- walk-sheet-page.md
- cut-export-page.md
- mailhog-page.md
- code-editor-page.md
- nocodb-page.md
- n8n-page.md
- gitea-page.md
### Missing Files (1)
-**page-editor-page.md**
- Referenced by: landing-pages-page.md, email-template-editor-page.md
- Source file exists: `admin/src/pages/PageEditorPage.tsx`
- Impact: **Medium** (breaks 2 internal links, important editor page)
---
## 2. Structural Validation
**Files with Complete Structure:** 28/29 (96.5%)
**Files with Missing Sections:** 1/29 (3.5%)
### Required Sections Checklist
- ✅ ## Overview
- ✅ ## Features
- ✅ ## User Workflow
- ✅ ## Component Structure/Breakdown
- ✅ ## API Integration
- ✅ ## Troubleshooting
- ✅ ## Related Documentation
### Structural Issues
- ⚠️ **dashboard-page.md:** Missing ## Troubleshooting section (minor impact)
---
## 3. Content Quality Metrics
### Documentation Volume
| Metric | Value |
|--------|-------|
| Total Lines | 28,457 |
| Average Lines per File | 981 |
| Largest File | mkdocs-settings-page.md (1,442 lines) |
| Smallest File | gitea-page.md (120 lines) |
### Code Examples
| Metric | Value |
|--------|-------|
| Total Code Blocks | 1,730 |
| Average per File | 59 blocks |
| Files with 100+ blocks | 3 files |
**Files with 100+ blocks:**
- landing-pages-page.md (110)
- listmonk-page.md (112)
- canvass-dashboard-page.md (102)
### API Endpoint Coverage
| Metric | Value |
|--------|-------|
| Total Endpoints Documented | ~185 |
| Average per File | 6 endpoints |
| Best Coverage | locations-page.md (21 endpoints) |
### Role-Based Access Documentation
| Metric | Value |
|--------|-------|
| Files mentioning RBAC | 17/29 (58.6%) |
| Files mentioning specific roles | 14/29 (48.3%) |
### Route Path Documentation
| Metric | Value |
|--------|-------|
| Files with route paths | 28/29 (96.5%) |
| Average route paths per file | 2.4 |
---
## 4. Code Block Quality
**Well-Formatted Files:** 28/29
### Issues Found
- ⚠️ **email-queue-page.md:** 5 code blocks without language tags
- Should specify: `typescript`, `json`, `bash`, etc.
**Recommendation:** Add language identifiers to improve syntax highlighting and readability
---
## 5. API Documentation Quality
**Files with Comprehensive API Docs:** 23/29 (79.3%)
(Including method, path, request/response examples)
### Service Pages Lacking Response Examples (6)
These files document API endpoints but don't show response formats:
- code-editor-page.md
- email-queue-page.md
- gitea-page.md
- mailhog-page.md
- n8n-page.md
- nocodb-page.md
**Impact:** Low-Medium
Service pages primarily document iframe integration, not direct API usage. However, response examples would improve completeness.
---
## 6. Cross-Reference Integrity
**Files with Broken Links:** 14/29 (48.3%)
### Categories of Broken Links
1. Backend module references (`api-reference/*`, `backend/modules/*`)
2. Feature documentation (`features/*`)
3. User guide references (`user-guides/*`)
4. Deployment guides (`deployment/*`)
5. Troubleshooting guides (`troubleshooting/*`)
### Most Impacted Files
| File | Broken Links |
|------|--------------|
| mkdocs-settings-page.md | 16 |
| observability-page.md | 16 |
| cut-export-page.md | 12 |
| mini-qr-page.md | 10 |
### Root Cause
Links reference documentation that doesn't exist yet in these directories:
- `v2/backend/`
- `v2/features/`
- `v2/api-reference/`
- `v2/user-guides/`
- `v2/deployment/`
- `v2/troubleshooting/`
### Recommendation
Either:
- a) Create the referenced documentation files
- b) Update links to point to existing documentation
- c) Remove forward references and mark as "Coming Soon"
---
## 7. File Size Distribution
### Large Files (1000+ lines): 11 files
- mkdocs-settings-page.md (1,442 lines)
- landing-pages-page.md (1,262 lines)
- representatives-page.md (1,218 lines)
- cuts-page.md (1,191 lines)
- map-settings-page.md (1,093 lines)
- canvass-dashboard-page.md (1,085 lines)
- listmonk-page.md (1,076 lines)
- locations-page.md (1,033 lines)
- email-queue-page.md (749 lines)
- email-templates-page.md (639 lines)
- email-template-editor-page.md (626 lines)
### Medium Files (400-999 lines): 12 files
### Small Files (<400 lines): 6 files
- gitea-page.md (120 lines)
- nocodb-page.md (190 lines)
- dashboard-page.md (194 lines)
- n8n-page.md (253 lines)
- code-editor-page.md (261 lines)
- settings-page.md (403 lines)
**Assessment:** Good balance between comprehensive coverage and maintainability. Smaller service pages are appropriately brief.
---
## 8. Consistency Analysis
### Consistent Patterns ✅
- All files follow H2 (`##`) section structure
- All files include TypeScript code examples
- All files document admin routes at `/app/*`
- All files include related documentation links
- All files use consistent terminology
### Component Documentation Style ✅
- Props tables with descriptions
- State management patterns
- Event handlers documented
- API integration patterns clear
### API Documentation Style ✅
- HTTP method + endpoint path
- Request parameters documented
- Most include response examples
- Error cases covered
---
## 9. Additional Insights from Deep Validation
### All 29 Files Have Proper H1 Titles ✅
- Every file starts with a descriptive H1 heading
- Titles follow consistent naming convention (e.g., "CampaignsPage", "UsersPage")
### Excellent Content Depth ✅
| Element | Count |
|---------|-------|
| State Management references (useState/useEffect/Zustand) | 380+ |
| Error Handling patterns | 87+ |
| JSON response examples | 126+ |
| TypeScript import statements | Shown in code examples |
### Comprehensive Workflow Documentation ✅
Sample file workflow step counts:
| File | Numbered Steps |
|------|----------------|
| locations-page.md | 145 |
| landing-pages-page.md | 108 |
| campaigns-page.md | 59 |
| canvass-dashboard-page.md | 47 |
| users-page.md | 33 |
### Rich Data Visualization ✅
Extensive use of tables for structured data:
| File | Table Rows |
|------|------------|
| locations-page.md | 22 |
| users-page.md | 14 |
| landing-pages-page.md | 9 |
| canvass-dashboard-page.md | 8 |
### Clear Access Control Documentation ✅
All major files document:
- Required authentication
- Specific role requirements (SUPER_ADMIN, MAP_ADMIN, INFLUENCE_ADMIN)
- Route paths (`/app/*`)
---
## 10. Recommendations
### Priority 1 (HIGH)
- [ ] **Create page-editor-page.md documentation**
- File exists: `admin/src/pages/PageEditorPage.tsx`
- Referenced by 2 other pages
- Important for landing page workflow
### Priority 2 (MEDIUM)
- [ ] **Fix cross-reference links** (14 files affected)
- Options: Create referenced docs, update links, or comment out forward references
- [ ] **Add response examples to 6 service pages**
- Improves API documentation completeness
- Helps developers understand integration
### Priority 3 (LOW)
- [ ] **Add Troubleshooting section to dashboard-page.md**
- For consistency with other pages
- [ ] **Add language tags to 5 code blocks in email-queue-page.md**
- Improves syntax highlighting
- [ ] **Consider splitting very large files (1000+ lines)**
- mkdocs-settings-page.md could be split by feature
- canvass-dashboard-page.md could separate components
---
## 11. Overall Quality Assessment
### Documentation Maturity: ★★★★☆ (4/5 stars)
### Strengths
- ✅ Comprehensive coverage (96.7% file completion)
- ✅ Excellent structural consistency (96.5% compliance)
- ✅ Rich code examples (1,730+ code blocks)
- ✅ Strong API documentation (185+ endpoints)
- ✅ Clear user workflows
- ✅ Good component breakdowns
- ✅ Extensive content (28,457 lines)
### Areas for Improvement
- ⚠️ 48% of files have broken cross-references
- ⚠️ Missing 1 critical page (page-editor)
- ⚠️ Some service pages lack response examples
- ⚠️ Minor code block formatting issues
### Impact on Users
- Documentation is **highly usable** in current state
- Internal navigation could be improved
- Cross-references need resolution for production-ready docs
---
## Final Recommendation
The admin page documentation is **PRODUCTION-READY** with one critical gap:
### 🔴 BLOCKER
- Create **page-editor-page.md** (referenced by 2 files, important workflow)
### 🟡 POLISH ITEMS (Non-blocking)
- Resolve 14 files with forward-reference links
- Add response examples to 6 service pages
- Add Troubleshooting section to dashboard-page.md
- Tag 5 code blocks in email-queue-page.md
### 📊 Metrics Summary
| Metric | Value |
|--------|-------|
| Files Complete | 29/30 (96.7%) |
| Total Lines | 28,457 |
| Code Examples | 1,730+ |
| API Endpoints | 185+ |
| Structural Compliance | 96.5% |
| Proper Titles | 100% |
| Route Documentation | 100% |
### ⭐ Quality Rating
**Current:** 4/5 stars (EXCELLENT)
**Once page-editor-page.md is created:** 4.5/5 stars (OUTSTANDING)
---
## Conclusion
This documentation represents an **excellent foundation** for the Changemaker Lite V2 admin interface. The comprehensive coverage, consistent structure, and rich code examples make this documentation immediately usable for developers.
The primary recommendation is to create the missing `page-editor-page.md` file to complete the documentation set. The broken cross-references are forward-looking links that can be resolved as the remaining documentation sections are created (backend, features, API reference, user guides, deployment, troubleshooting).
**Overall Verdict:** Ready for initial use with minor improvements needed for production-grade completeness.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

View File

@@ -102,14 +102,20 @@
/* Responsive video containers */
@media (max-width: 768px) {
.video-block,
.advanced-video-container {
.advanced-video-container,
.video-card-block {
margin: 1.5rem auto;
}
.video-block video,
.advanced-video-container video {
.advanced-video-container video,
.video-card-player video {
border-radius: 4px;
}
.video-card-block {
max-width: 100%;
}
}
/* Video placeholder (shown during GrapesJS export before hydration) */
@@ -138,9 +144,45 @@
margin: 0;
}
/* Video card block (YouTube-style preview card) */
.video-card-block {
margin: 2rem auto;
max-width: 480px;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.video-card-block:hover {
transform: translateY(-2px);
}
.video-card-block a {
text-decoration: none !important;
color: inherit !important;
}
/* Video card gallery button */
.video-card-gallery-btn:active {
transform: scale(0.96);
}
/* Video card inline player (after clicking thumbnail) */
.video-card-player video {
width: 100%;
display: block;
background: #000;
}
.video-card-player video:focus {
outline: 2px solid var(--md-primary-fg-color);
outline-offset: 2px;
}
/* Ensure videos respect content width in Material theme */
.md-content .video-block,
.md-content .advanced-video-container {
.md-content .advanced-video-container,
.md-content .video-card-block {
max-width: 100%;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

View File

@@ -2,7 +2,7 @@
// Generated by mkdocs/docs/hooks/env_config_hook.py
(function() {
window.MEDIA_API_URL = 'http://localhost:4100';
window.PUBLIC_URL = 'http://localhost:3000';
window.PUBLIC_URL = 'http://localhost:3002';
window.VIDEO_PLAYER_DEBUG = false;
console.log('[Video Config] Loaded from environment:', {

View File

@@ -189,14 +189,202 @@
.replace(/'/g, '&#039;');
}
/**
* Format view count (e.g. 1200 → "1.2K views")
*/
function formatViewCount(count) {
if (!count || count <= 0) return '0 views';
if (count === 1) return '1 view';
if (count < 1000) return count + ' views';
if (count < 1000000) return (count / 1000).toFixed(1).replace(/\.0$/, '') + 'K views';
return (count / 1000000).toFixed(1).replace(/\.0$/, '') + 'M views';
}
/**
* Get the gallery URL for a video.
*/
function getGalleryUrl(videoId) {
return PUBLIC_URL + '/gallery?expanded=' + videoId;
}
/**
* Hydrate a video card block:
* 1. Fix thumbnail URL using MEDIA_API_URL
* 2. Fetch live metadata (views, title)
* 3. Clicking thumbnail plays video inline
* 4. Replace "Watch →" with Gallery link
*/
async function hydrateVideoCard(cardElement) {
var videoId = parseInt(cardElement.dataset.videoId, 10);
if (!videoId || isNaN(videoId)) return;
var title = cardElement.dataset.videoTitle || 'Video';
// 1. Fix thumbnail URL — replace placeholder/broken src with MEDIA_API_URL
var img = cardElement.querySelector('img');
if (img) {
// Cache-buster avoids stale CORS-error cached responses
var correctThumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail?v=' + Date.now();
// Always replace src — data URI placeholder, Docker hostname, or stale URL
img.src = correctThumbUrl;
// Fallback if thumbnail fails to load — show a styled placeholder instead of broken image
img.onerror = function() {
img.style.display = 'none';
};
}
// 2. Fetch live metadata to update views/title
try {
var metadata = await fetchVideoMetadata(videoId);
if (metadata) {
var viewsEl = cardElement.querySelector('[data-role="views"]');
if (viewsEl && metadata.viewCount !== undefined) {
viewsEl.textContent = formatViewCount(metadata.viewCount);
}
}
} catch (e) {
// Non-critical — card works fine without live metadata
}
// 3. Prevent the <a> tag from navigating
var link = cardElement.querySelector('a');
if (link) {
link.addEventListener('click', function(e) { e.preventDefault(); });
}
// 4. Fix the play button overlay SVG + set up click-to-play
var thumbContainer = cardElement.querySelector('a > div:first-child');
if (thumbContainer) {
// Replace old tiny play icon with proper triangle
var playOverlay = thumbContainer.querySelector('div:last-child');
if (playOverlay && playOverlay.querySelector('svg')) {
playOverlay.innerHTML = '<svg width="28" height="28" viewBox="0 0 24 24" fill="#fff" style="margin-left:3px;"><polygon points="5,3 19,12 5,21"/></svg>';
}
thumbContainer.style.cursor = 'pointer';
thumbContainer.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
// Don't re-create if already playing
if (cardElement.querySelector('video')) return;
var streamUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/stream';
var thumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail';
// Build inline player — locked to same 16:9 aspect ratio as the card
var playerContainer = document.createElement('div');
playerContainer.className = 'video-card-player';
playerContainer.style.cssText = 'max-width:480px;margin:0 auto;border-radius:12px;overflow:hidden;background:#0d1b2a;box-shadow:0 4px 12px rgba(0,0,0,0.3);';
// 16:9 aspect ratio wrapper prevents container resize when video loads
var videoWrapper = document.createElement('div');
videoWrapper.style.cssText = 'position:relative;padding-bottom:56.25%;background:#000;';
var video = document.createElement('video');
video.src = streamUrl;
video.poster = thumbUrl;
video.controls = true;
video.autoplay = true;
video.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;display:block;border-radius:12px 12px 0 0;';
video.setAttribute('title', title);
videoWrapper.appendChild(video);
var infoBar = document.createElement('div');
infoBar.style.cssText = 'padding:10px 16px;background:#1b2838;display:flex;align-items:center;gap:8px;';
var titleSpan = document.createElement('span');
titleSpan.style.cssText = 'color:#fff;font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;';
titleSpan.textContent = title;
var galleryLink = document.createElement('a');
galleryLink.href = getGalleryUrl(videoId);
galleryLink.target = '_blank';
galleryLink.innerHTML = '&#9654; Gallery';
galleryLink.className = 'video-card-gallery-btn';
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 12px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background 0.15s;text-decoration:none;display:inline-block;';
galleryLink.title = 'Open in gallery';
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
galleryLink.addEventListener('click', function(ev) {
ev.stopPropagation();
});
var closeBtn = document.createElement('button');
closeBtn.textContent = '\u2715';
closeBtn.style.cssText = 'background:none;border:none;color:#8899aa;font-size:16px;cursor:pointer;padding:4px;flex-shrink:0;transition:color 0.15s;';
closeBtn.title = 'Close player';
closeBtn.onmouseenter = function() { closeBtn.style.color = '#fff'; };
closeBtn.onmouseleave = function() { closeBtn.style.color = '#8899aa'; };
infoBar.appendChild(titleSpan);
infoBar.appendChild(galleryLink);
infoBar.appendChild(closeBtn);
playerContainer.appendChild(videoWrapper);
playerContainer.appendChild(infoBar);
// Save original card content and replace
var originalContent = cardElement.innerHTML;
cardElement.innerHTML = '';
cardElement.appendChild(playerContainer);
// Close button restores original card
closeBtn.addEventListener('click', function(ev) {
ev.stopPropagation();
video.pause();
video.src = '';
cardElement.innerHTML = originalContent;
hydrateVideoCard(cardElement);
});
});
}
// 5. Replace "Watch →" text with a Gallery button in the card info bar
var infoBar = cardElement.querySelector('a > div:last-child');
if (infoBar) {
var flexRow = infoBar.querySelector('div:last-child');
if (flexRow) {
var spans = flexRow.querySelectorAll('span');
var watchSpan = null;
for (var i = 0; i < spans.length; i++) {
if (spans[i].textContent.indexOf('Watch') !== -1) {
watchSpan = spans[i];
break;
}
}
if (watchSpan) {
var galleryLink = document.createElement('a');
galleryLink.href = getGalleryUrl(videoId);
galleryLink.target = '_blank';
galleryLink.innerHTML = '&#9654; Gallery';
galleryLink.className = 'video-card-gallery-btn';
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 14px;border-radius:4px;cursor:pointer;transition:background 0.15s;text-decoration:none;display:inline-block;';
galleryLink.title = 'Open in gallery';
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
galleryLink.addEventListener('click', function(ev) {
ev.stopPropagation(); // Don't trigger card click-to-play
});
watchSpan.replaceWith(galleryLink);
}
}
}
// Mark as hydrated
cardElement.dataset.hydrated = 'true';
console.log('Hydrated video card for video ' + videoId);
}
/**
* Initialize all video blocks on page load
*/
function initVideoBlocks() {
const blocks = document.querySelectorAll('.video-block');
console.log(`Found ${blocks.length} video block(s) to hydrate`);
var blocks = document.querySelectorAll('.video-block');
console.log('Found ' + blocks.length + ' video block(s) to hydrate');
blocks.forEach(block => {
blocks.forEach(function(block) {
// Skip if already rendered (has video element)
if (block.querySelector('video')) {
console.log('Video block already rendered, skipping');
@@ -207,18 +395,40 @@
});
}
/**
* Initialize all video card blocks on page load
*/
function initVideoCards() {
var cards = document.querySelectorAll('.video-card-block');
console.log('Found ' + cards.length + ' video card(s) to hydrate');
cards.forEach(function(card) {
// Skip if already hydrated
if (card.dataset.hydrated === 'true') return;
hydrateVideoCard(card);
});
}
/**
* Initialize all video elements (blocks + cards)
*/
function initAll() {
initVideoBlocks();
initVideoCards();
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initVideoBlocks);
document.addEventListener('DOMContentLoaded', initAll);
} else {
initVideoBlocks();
initAll();
}
// Re-initialize on MkDocs navigation (for SPA-style navigation)
if (typeof window.document$ !== 'undefined') {
window.document$.subscribe(() => {
console.log('MkDocs navigation detected, re-initializing video blocks');
setTimeout(initVideoBlocks, 100);
window.document$.subscribe(function() {
console.log('MkDocs navigation detected, re-initializing video elements');
setTimeout(initAll, 100);
});
}
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 KiB

View File

@@ -7,10 +7,10 @@
"stars_count": 0,
"forks_count": 0,
"open_issues_count": 23,
"updated_at": "2026-02-11T10:19:49-07:00",
"updated_at": "2026-02-17T10:36:57-07:00",
"created_at": "2025-05-28T14:54:59-06:00",
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
"default_branch": "main",
"last_build_update": "2026-02-11T10:19:49-07:00"
"last_build_update": "2026-02-17T10:36:57-07:00"
}

View File

@@ -3,14 +3,14 @@
"name": "claude-code",
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
"html_url": "https://github.com/anthropics/claude-code",
"language": "PowerShell",
"stars_count": 26166,
"forks_count": 1437,
"open_issues_count": 2513,
"updated_at": "2025-07-27T00:10:38Z",
"language": "Shell",
"stars_count": 67351,
"forks_count": 5255,
"open_issues_count": 6188,
"updated_at": "2026-02-17T21:23:40Z",
"created_at": "2025-02-22T17:41:21Z",
"clone_url": "https://github.com/anthropics/claude-code.git",
"ssh_url": "git@github.com:anthropics/claude-code.git",
"default_branch": "main",
"last_build_update": "2025-07-25T21:06:46Z"
"last_build_update": "2026-02-17T18:53:52Z"
}

View File

@@ -4,13 +4,13 @@
"description": "VS Code in the browser",
"html_url": "https://github.com/coder/code-server",
"language": "TypeScript",
"stars_count": 73102,
"forks_count": 6130,
"open_issues_count": 143,
"updated_at": "2025-07-26T22:12:45Z",
"stars_count": 76278,
"forks_count": 6513,
"open_issues_count": 176,
"updated_at": "2026-02-17T20:24:30Z",
"created_at": "2019-02-27T16:50:41Z",
"clone_url": "https://github.com/coder/code-server.git",
"ssh_url": "git@github.com:coder/code-server.git",
"default_branch": "main",
"last_build_update": "2025-07-24T23:16:29Z"
"last_build_update": "2026-02-14T12:44:57Z"
}

View File

@@ -4,13 +4,13 @@
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
"html_url": "https://github.com/gethomepage/homepage",
"language": "JavaScript",
"stars_count": 25016,
"forks_count": 1560,
"stars_count": 28451,
"forks_count": 1790,
"open_issues_count": 1,
"updated_at": "2025-07-26T22:55:16Z",
"updated_at": "2026-02-17T19:07:13Z",
"created_at": "2022-08-24T07:29:42Z",
"clone_url": "https://github.com/gethomepage/homepage.git",
"ssh_url": "git@github.com:gethomepage/homepage.git",
"default_branch": "dev",
"last_build_update": "2025-07-27T00:42:35Z"
"last_build_update": "2026-02-17T12:23:04Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
"html_url": "https://github.com/go-gitea/gitea",
"language": "Go",
"stars_count": 49740,
"forks_count": 5920,
"open_issues_count": 2736,
"updated_at": "2025-07-27T00:44:09Z",
"stars_count": 53755,
"forks_count": 6390,
"open_issues_count": 2835,
"updated_at": "2026-02-17T20:31:54Z",
"created_at": "2016-11-01T02:13:26Z",
"clone_url": "https://github.com/go-gitea/gitea.git",
"ssh_url": "git@github.com:go-gitea/gitea.git",
"default_branch": "main",
"last_build_update": "2025-07-27T00:44:04Z"
"last_build_update": "2026-02-17T20:32:46Z"
}

View File

@@ -4,13 +4,13 @@
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
"html_url": "https://github.com/knadh/listmonk",
"language": "Go",
"stars_count": 17468,
"forks_count": 1686,
"open_issues_count": 100,
"updated_at": "2025-07-26T16:53:36Z",
"stars_count": 19074,
"forks_count": 1926,
"open_issues_count": 113,
"updated_at": "2026-02-17T16:51:23Z",
"created_at": "2019-06-26T05:08:39Z",
"clone_url": "https://github.com/knadh/listmonk.git",
"ssh_url": "git@github.com:knadh/listmonk.git",
"default_branch": "master",
"last_build_update": "2025-07-22T12:07:13Z"
"last_build_update": "2026-02-17T05:10:24Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
"html_url": "https://github.com/lyqht/mini-qr",
"language": "Vue",
"stars_count": 1338,
"forks_count": 177,
"open_issues_count": 13,
"updated_at": "2025-07-26T18:13:59Z",
"stars_count": 1851,
"forks_count": 236,
"open_issues_count": 21,
"updated_at": "2026-02-17T15:35:23Z",
"created_at": "2023-04-21T14:20:14Z",
"clone_url": "https://github.com/lyqht/mini-qr.git",
"ssh_url": "git@github.com:lyqht/mini-qr.git",
"default_branch": "main",
"last_build_update": "2025-07-17T12:31:42Z"
"last_build_update": "2025-10-31T15:20:31Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
"html_url": "https://github.com/n8n-io/n8n",
"language": "TypeScript",
"stars_count": 123887,
"forks_count": 37509,
"open_issues_count": 971,
"updated_at": "2025-07-27T00:44:16Z",
"stars_count": 174947,
"forks_count": 54925,
"open_issues_count": 1388,
"updated_at": "2026-02-17T21:23:05Z",
"created_at": "2019-06-22T09:24:21Z",
"clone_url": "https://github.com/n8n-io/n8n.git",
"ssh_url": "git@github.com:n8n-io/n8n.git",
"default_branch": "master",
"last_build_update": "2025-07-26T15:16:15Z"
"last_build_update": "2026-02-17T21:12:15Z"
}

View File

@@ -1,16 +1,16 @@
{
"full_name": "nocodb/nocodb",
"name": "nocodb",
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 Open Source Airtable Alternative",
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
"html_url": "https://github.com/nocodb/nocodb",
"language": "TypeScript",
"stars_count": 56041,
"forks_count": 4048,
"open_issues_count": 686,
"updated_at": "2025-07-26T23:44:10Z",
"stars_count": 62026,
"forks_count": 4632,
"open_issues_count": 592,
"updated_at": "2026-02-17T20:03:56Z",
"created_at": "2017-10-29T18:51:48Z",
"clone_url": "https://github.com/nocodb/nocodb.git",
"ssh_url": "git@github.com:nocodb/nocodb.git",
"default_branch": "develop",
"last_build_update": "2025-07-26T18:53:06Z"
"last_build_update": "2026-02-17T18:50:24Z"
}

View File

@@ -1,16 +1,16 @@
{
"full_name": "ollama/ollama",
"name": "ollama",
"description": "Get up and running with Llama 3.3, DeepSeek-R1, Phi-4, Gemma 3, Mistral Small 3.1 and other large language models.",
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
"html_url": "https://github.com/ollama/ollama",
"language": "Go",
"stars_count": 147622,
"forks_count": 12527,
"open_issues_count": 1947,
"updated_at": "2025-07-27T00:34:59Z",
"stars_count": 162779,
"forks_count": 14601,
"open_issues_count": 2431,
"updated_at": "2026-02-17T20:40:13Z",
"created_at": "2023-06-26T19:39:32Z",
"clone_url": "https://github.com/ollama/ollama.git",
"ssh_url": "git@github.com:ollama/ollama.git",
"default_branch": "main",
"last_build_update": "2025-07-25T23:58:11Z"
"last_build_update": "2026-02-17T21:16:23Z"
}

View File

@@ -4,13 +4,13 @@
"description": "Documentation that simply works",
"html_url": "https://github.com/squidfunk/mkdocs-material",
"language": "Python",
"stars_count": 24011,
"forks_count": 3825,
"open_issues_count": 6,
"updated_at": "2025-07-27T00:11:59Z",
"stars_count": 26063,
"forks_count": 4044,
"open_issues_count": 2,
"updated_at": "2026-02-17T20:20:41Z",
"created_at": "2016-01-28T22:09:23Z",
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
"default_branch": "master",
"last_build_update": "2025-07-26T15:53:16Z"
"last_build_update": "2026-01-21T14:19:54Z"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 KiB

View File

@@ -0,0 +1,2 @@
# Blog

View File

@@ -1,9 +0,0 @@
---
date: 2025-07-03
---
# Blog 1
Hello! Just putting something up here because, well, gosh darn, feels like the right thing to do.
Making swift progress. Can now write things fast as heck lad.

View File

@@ -1,14 +0,0 @@
---
date: 2025-07-10
---
Wow. Big build day. Added (admittedly still buggy) shifts support to the system. Power did it in a day.
Other updates recently include:
- Fully reworked backend `server.js` into modular components.
- Bunch of mobile related fixes and improvements.
- Bi-directional saving of configs fixed up
- Some style upgrades
Need to make more content about how to use the system in general too.

View File

@@ -1,74 +0,0 @@
---
date: 2025-08-01
---
Alrighty yall, it was a wild month of development, and we have a lot to cover! Heres the latest on Changemaker Lite, including our new landing page, major updates to the map application, and a comprehensive overview of all changes made in the last month.
Campaigning is going! We have candidates working the system in the field, and were excited to see how it performs in real-world scenarios.
# Monthly Development Report August 2025
## Git Change Summary (JulyAugust 2025)
Below is a summary of all changes pushed to git in the last month:
- **Admin Panel & NocoDB Integration**: Major updates to the admin section, including a new NocoDB admin area, improved database search, and code cleanups.
- **Website & UI Updates**: Numerous updates to the website, including language tweaks, mobile friendliness, and new frontend features.
- **Shifts Management**: Comprehensive volunteer shift management system added, with calendar/grid views, admin controls, and real-time updates.
- **Authentication & User Management**: Enhanced login system, password recovery via SMTP, user management panel for admins, and role-based access control.
- **Map & Geocoding**: Improved map display, apartment views, geocoding integration, and address confirmation system.
- **Unified Search System**: Powerful search bar (Ctrl+K) for docs and address search, with real-time results, caching, and QR code generation.
- **Data Import & Conversion**: CSV data import with batch geocoding and visual progress, plus a new data converter tool.
- **Email & Notifications**: SMTP integration for email notifications and password recovery.
- **Performance & Bug Fixes**: Numerous bug fixes, code cleanups, and performance improvements across the stack.
- **Docker & Deployment**: Docker containerization, improved build scripts, and easier multi-instance deployment.
- **Documentation**: Expanded and updated documentation, including new manuals and guides.
For a detailed commit log, see `git-report.txt`.
---
## Overview of `lander.html`
The `lander.html` file is a modern, responsive landing page for Changemaker Lite, featuring:
- **Custom Theming**: Light/dark mode toggle with persistent user preference.
- **Sticky Header & Navigation**: Fixed header with smooth scroll and navigation links.
- **Hero Section**: Prominent introduction with call-to-action buttons.
- **Search Integration**: Inline MkDocs search with real-time results and keyboard shortcuts.
- **Feature Showcases**: Sections for problems, solutions, power tools, data ownership, pricing, integrations, testimonials, and live examples.
- **Responsive Design**: Mobile-friendly layout with adaptive grids and cards.
- **Animations**: Intersection observer for fade-in effects on cards and sections.
- **Video & Media**: Embedded video showcase and rich media support.
- **Footer**: Informative footer with links and contact info.
The page is styled with CSS variables for easy theming and includes scripts for search, theme switching, and smooth scrolling.
---
## New Features in Map (`README.md`)
The map application has received significant upgrades:
- **Interactive Map**: Real-time visualization with OpenStreetMap and Leaflet.js.
- **Unified Search**: Docs and address search in one bar, with keyboard shortcuts and smart caching.
- **Geolocation & Add Locations**: Real-time user geolocation and ability to add new locations directly from the map.
- **Auto-Refresh**: Map data auto-refreshes every 30 seconds.
- **Responsive & Mobile Ready**: Fully responsive design for all devices.
- **Secure API Proxy**: Protects credentials and secures API access.
- **Admin Panel**: System configuration, user management, and shift management for admins.
- **Walk Sheet Generator**: For door-to-door canvassing, with customizable titles and QR code integration.
- **Volunteer Shifts**: Calendar/grid views, signup/cancellation, admin shift creation, and real-time updates.
- **Role-Based Access**: Admin vs. user permissions throughout the app.
- **Email Notifications**: SMTP-based notifications and password recovery.
- **CSV Import & Geocoding**: Batch import with geocoding and progress tracking.
- **Dockerized Deployment**: Easy setup and scaling with Docker.
- **Open Source**: 100% open source, no proprietary dependencies.
**API Endpoints**: Comprehensive REST API for locations, shifts, authentication, admin, and geocoding, all with rate limiting and security features.
**Database Schema**: Auto-created tables for locations, users, settings, shifts, and signups, with detailed field definitions.
---
For more details, see the full `README.md` and explore the live application.

View File

@@ -1,42 +0,0 @@
---
date: 2025-09-24
---
Okay! Wow! Its been nearly 2 months since I wrote a blog update for this system.
We have pushed out [influence](https://influence.bnkops.com) as a beta product, and will be pushing it out to get feedback from real users over the next month.
Our campaign software Map was also used by a real campaign for the first time, and we have some great feedback to incorporate into the system.
## What We've Built Since August
Here's a quick rundown of everything we've committed to the codebase over the past three months:
### Influence App - Major Launch
- **Complete UI Overhaul**: Built an entirely new user interface and user system from the ground up
- **Response Wall**: Developed a comprehensive response wall system where elected officials can respond to campaigns, including verified response system with QR codes and verify buttons
- **Campaign Management**: Created new system for creating campaigns from the main site dashboard with campaign cover photos and phone numbers
- **Social Features**: Added social share buttons and site info improvements
- **Geocoding Enhancements**: Implemented automatic scanning of NocoDB locations to build geo-locations, plus premium Mapbox option for better street address matching
- **User Management**: Built password updater for users/admins and improved overall user management
- **Network Integration**: Integrated Influence into the Changemaker network
- **Monitoring & Maintenance**: Added health check utility, logger, metrics, backup, and SMTP toggle scripts
### Map App - Production Ready
- **Map Cuts Feature**: Built a comprehensive "cuts" system for dividing territories, including assignment workflows, print views, and spatial data handling
- **Public Shifts**: Implemented new public shifts system for volunteer coordination
- **Performance**: Optimized loading for maps with 1000+ locations and improved shift loading speeds
- **Admin Improvements**: Major refactor of admin.js into readable, maintainable files, plus new NocoDB admin section with database search
- **Temp Users**: Enhanced temporary user system with proper access controls and limited data sending
- **Data Tools**: Added CSV import reporting and ListMonk synchronization
- **UI/UX**: Standardized z-indexes, updated pop-ups, fixed menu bugs, and improved cut overlays
- **CORS & Auth**: Fixed authentication, lockouts, and CORS for local dev access
### Infrastructure & DevOps
- **Documentation**: Updated MkDocs documentation with search functionality
- **Build System**: Improved build-nocodb script to migrate data and auto-input URLs to .env
- **Docker**: Cleaned up docker-compose configuration and fixed container duplication issues
- **Configuration**: Updated homepage configs, Cloudflare tunnel settings, and general system configs
The velocity has been incredible - we went from concept to production with Influence in just a few weeks, and Map has evolved into a robust campaigning tool that's battle-tested in real elections. Looking forward to incorporating user feedback and continuing to iterate!

View File

@@ -0,0 +1,22 @@
---
title: Administration
description: Admin guide for managing users, settings, and platform operations.
icon: material/shield-account
---
# Administration
This section covers day-to-day administration of the Changemaker Lite platform.
!!! warning "Under Construction"
Detailed admin documentation is being written. Check back soon.
## Topics
- **User Management** — create, edit, and deactivate user accounts
- **Roles & Permissions** — SUPER_ADMIN, INFLUENCE_ADMIN, MAP_ADMIN, USER, TEMP
- **Site Settings** — configure site name, contact info, and feature flags
- **Email Templates** — manage reusable email templates
- **Newsletter Sync** — Listmonk integration for subscriber management
- **Email Queue** — monitor and manage the BullMQ advocacy email queue
- **Landing Pages** — create and publish pages with the GrapesJS editor

View File

@@ -0,0 +1,42 @@
---
title: API Reference
description: REST API endpoints for authentication, campaigns, locations, media, and more.
icon: material/api
---
# API Reference
Changemaker Lite exposes two REST APIs — the main Express API (port 4000) and the Fastify Media API (port 4100).
!!! warning "Under Construction"
The full API reference is being written. Check back soon.
## Main API (Express, port 4000)
### Authentication
- `POST /api/auth/register` — create account
- `POST /api/auth/login` — get access + refresh tokens
- `POST /api/auth/refresh` — rotate refresh token
- `POST /api/auth/logout` — invalidate refresh token
- `GET /api/auth/me` — current user profile
### Campaigns
- `GET /api/campaigns` — list campaigns (admin)
- `POST /api/campaigns` — create campaign
- `GET /api/public/campaigns` — list public campaigns
- `GET /api/public/campaigns/:id` — campaign details
### Locations
- `GET /api/locations` — list locations (admin)
- `POST /api/locations` — create location
- `GET /api/public/locations` — public map locations
### More Endpoints
Users, shifts, cuts, responses, email queue, pages, settings, and more — full reference coming soon.
## Media API (Fastify, port 4100)
- `GET /api/videos` — list videos
- `POST /api/upload` — upload video
- `GET /api/public/videos` — public gallery
- `POST /api/track/view` — record view event

View File

@@ -0,0 +1,39 @@
---
title: Architecture
description: System architecture, dual API design, database schema, and authentication flow.
icon: material/sitemap
---
# Architecture
Changemaker Lite uses a dual-API architecture with a shared PostgreSQL database.
!!! warning "Under Construction"
Detailed architecture documentation is being written. Check back soon.
## System Overview
```
Browser ──► Nginx (reverse proxy) ──┬──► Express API (port 4000) ──► PostgreSQL
├──► Fastify Media API (port 4100) ──┘
├──► React Admin GUI (port 3000)
└──► MkDocs / Other Services
```
## Key Components
| Component | Technology | Role |
|-----------|-----------|------|
| Main API | Express.js + Prisma | Auth, campaigns, map, shifts, pages |
| Media API | Fastify + Prisma | Video library, analytics, uploads |
| Admin GUI | React + Ant Design | Single-page admin application |
| Database | PostgreSQL 16 | Shared by both APIs (30+ tables) |
| Cache | Redis | Rate limiting, job queues, geocoding |
| Proxy | Nginx | Subdomain routing, security headers |
## Authentication Flow
- JWT access tokens (15 min) + refresh tokens (7 days)
- Refresh token rotation with atomic database transaction
- Role-based access control (5 roles)
- Rate limiting on auth endpoints (10/min per IP)

View File

@@ -0,0 +1,579 @@
---
title: Deployment
description: Deploy Changemaker Lite to production with Docker, SSL, backups, and monitoring.
icon: material/docker
---
# Deployment
This guide covers how to take Changemaker Lite from a local development setup to a publicly accessible production deployment. The main decision is **how to expose your services to the internet**.
## Architecture Overview
Regardless of which exposure method you choose, the internal architecture is the same:
```
Internet → [Your exposure method] → Nginx (port 80) → Backend Services
```
Nginx handles all subdomain routing internally. Every service is accessed through nginx on port 80, which proxies to the correct container based on the `Host` header.
| Subdomain | Service | Container Port |
|-----------|---------|---------------|
| `app.DOMAIN` | Admin GUI + public pages | 3000 |
| `api.DOMAIN` | Express API | 4000 |
| `media.DOMAIN` | Fastify Media API | 4100 |
| `DOMAIN` (root) | MkDocs documentation site | 4001 |
| `db.DOMAIN` | NocoDB | 8091 |
| `docs.DOMAIN` | MkDocs live preview | 4003 |
| `code.DOMAIN` | Code Server | 8888 |
| `git.DOMAIN` | Gitea | 3030 |
| `n8n.DOMAIN` | Workflow automation | 5678 |
| `home.DOMAIN` | Homepage dashboard | 3010 |
| `listmonk.DOMAIN` | Newsletter manager | 9001 |
| `mail.DOMAIN` | MailHog (dev email) | 8025 |
| `qr.DOMAIN` | Mini QR generator | 8089 |
| `draw.DOMAIN` | Excalidraw whiteboard | 8090 |
| `grafana.DOMAIN` | Monitoring dashboards | 3001 |
---
## Exposure Methods
### Option 1: Pangolin + Newt Tunnel (Recommended) { #pangolin }
!!! tip "Admin GUI: Tunnel Management Page"
The admin dashboard includes a dedicated **Tunnel Management** page at **Admin &rarr; Settings &rarr; Tunnel**. This page provides:
- **Live status** of the Pangolin connection and Newt container health
- **Step-by-step setup instructions** if credentials aren't configured yet
- **Full resource table** listing every service, its domain, and target &mdash; useful as a reference when creating resources in the Pangolin dashboard
- **API-based site creation** as an alternative to the Pangolin dashboard UI
- **Restart Newt** button for quick container restarts without the terminal
If you're unsure about any step above, the Tunnel page walks you through the same process interactively.
[Pangolin](https://github.com/fosrl/pangolin) is a self-hosted tunnel server. The **Newt** client container runs alongside your stack and establishes an outbound connection to your Pangolin server, which then routes public traffic back through the tunnel. No port forwarding or static IP required.
**Advantages:**
- No port forwarding needed on your router/firewall
- Works behind CGNAT, double NAT, or restrictive networks
- SSL/TLS handled by the Pangolin server
- Self-hosted &mdash; you control the tunnel infrastructure
- Built-in access control (optional per-resource authentication)
**Requirements:**
- A Pangolin server (self-hosted on a VPS with a public IP)
- A domain with DNS pointing to the Pangolin server
- Pangolin API key and organization ID
#### Step 1: Configure Pangolin Credentials
If you used `config.sh`, you may have already set these. Otherwise, add to your `.env`:
```bash
PANGOLIN_API_URL=https://api.your-pangolin-server.org/v1
PANGOLIN_API_KEY=your_api_key_here
PANGOLIN_ORG_ID=your_org_id
```
#### Step 2: Create a Site in Pangolin
Log in to your Pangolin dashboard and create a new site:
1. Navigate to **Sites** &rarr; **Create New Site**
2. Choose type: **Newt**
3. Enter a name (e.g., `changemaker-yourdomain.org`)
4. Choose a subnet (e.g., `100.90.128.3/24`)
5. Select an exit node (if applicable)
6. Click **Create Site**
7. **Copy the credentials** &mdash; you'll need the Site ID, Newt ID, and Newt Secret
!!! warning "Save the credentials"
The Newt Secret is only shown once during site creation. Copy it immediately.
#### Step 3: Update `.env` with Site Credentials
```bash
PANGOLIN_SITE_ID=your_site_id
PANGOLIN_ENDPOINT=https://your-pangolin-server.org
PANGOLIN_NEWT_ID=your_newt_id
PANGOLIN_NEWT_SECRET=your_newt_secret
```
#### Step 4: Start the Newt Container
```bash
docker compose up -d newt
```
The Newt container connects to nginx (its only dependency) and establishes the tunnel:
```yaml
# From docker-compose.yml
newt:
image: fosrl/newt
container_name: newt-changemaker
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT}
- NEWT_ID=${PANGOLIN_NEWT_ID}
- NEWT_SECRET=${PANGOLIN_NEWT_SECRET}
depends_on:
- nginx
```
Verify the connection:
```bash
docker compose logs newt --tail 20
```
You should see a successful connection message.
#### Step 5: Create Public HTTP Resources
In the Pangolin dashboard, create an HTTP resource for each service you want exposed. All resources point to `nginx:80` &mdash; nginx handles the routing internally.
**Required resources** (minimum for a working deployment):
| Resource Name | Domain | Target | Auth |
|--------------|--------|--------|------|
| Admin GUI | `app.yourdomain.org` | `nginx:80` | Not Protected |
| API Server | `api.yourdomain.org` | `nginx:80` | Not Protected |
| Public Site | `yourdomain.org` | `nginx:80` | Not Protected |
**Optional resources** (add as needed):
| Resource Name | Domain | Target |
|--------------|--------|--------|
| Media API | `media.yourdomain.org` | `nginx:80` |
| NocoDB | `db.yourdomain.org` | `nginx:80` |
| Documentation | `docs.yourdomain.org` | `nginx:80` |
| Code Server | `code.yourdomain.org` | `nginx:80` |
| Gitea | `git.yourdomain.org` | `nginx:80` |
| Grafana | `grafana.yourdomain.org` | `nginx:80` |
!!! danger "Set resources to Not Protected"
By default, Pangolin may enable authentication on new resources. This causes 302 redirects to the Pangolin login page instead of reaching your services. Set each resource to **Not Protected** (public access) unless you intentionally want Pangolin SSO in front of it.
#### Step 6: Update CORS for Production
Add your production domain to `CORS_ORIGINS` in `.env`:
```bash
CORS_ORIGINS=https://app.yourdomain.org,http://localhost:3000,http://localhost
```
Then restart the API:
```bash
docker compose restart api
```
#### Step 7: Verify
```bash
# Should return JSON (not a 302 redirect)
curl https://api.yourdomain.org/api/health
# Admin GUI should load
curl -I https://app.yourdomain.org
```
---
### Option 2: Cloudflare Tunnel { #cloudflare }
Cloudflare Tunnel (`cloudflared`) provides a similar zero-trust tunnel approach using Cloudflare's network. No port forwarding needed, and you get Cloudflare's CDN and DDoS protection.
**Advantages:**
- Free tier available
- Built-in CDN and DDoS protection
- No port forwarding needed
- Managed SSL certificates
**Disadvantages:**
- Proprietary service (not self-hosted)
- Cloudflare sees all traffic (no end-to-end encryption to your origin)
- Subject to Cloudflare's Terms of Service
#### Setup
1. **Create a Cloudflare Tunnel** in the [Zero Trust dashboard](https://one.dash.cloudflare.com/)
2. **Add a `cloudflared` service** to your `docker-compose.yml`:
```yaml
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared-changemaker
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
depends_on:
- nginx
networks:
- changemaker-lite
```
3. **Add your tunnel token** to `.env`:
```bash
CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here
```
4. **Configure public hostnames** in the Cloudflare dashboard, all pointing to `http://nginx:80`:
| Hostname | Service |
|----------|---------|
| `app.yourdomain.org` | `http://nginx:80` |
| `api.yourdomain.org` | `http://nginx:80` |
| `yourdomain.org` | `http://nginx:80` |
| *(add more as needed)* | `http://nginx:80` |
5. **Start the tunnel:**
```bash
docker compose up -d cloudflared
```
!!! note
The `cloudflared` service is not included in the default `docker-compose.yml`. Add it manually if you choose this method. The Newt service can be removed or left stopped.
---
### Option 3: Direct DNS + Reverse Proxy { #direct }
If your server has a public IP address (e.g., a VPS or dedicated server), you can point DNS directly to it and use nginx with SSL certificates.
**Advantages:**
- No tunnel overhead or third-party dependency
- Full control over the network path
- Lowest latency
**Disadvantages:**
- Requires a public IP and open ports (80, 443)
- You manage SSL certificates yourself
- Server IP is exposed
#### Setup
1. **Point DNS** for your domain and all subdomains to your server's IP:
```
A yourdomain.org → YOUR_SERVER_IP
A *.yourdomain.org → YOUR_SERVER_IP
```
Or use individual A records for each subdomain if your DNS provider doesn't support wildcards.
2. **Open ports** 80 and 443 on your server's firewall.
3. **Install Certbot** (or another ACME client) for SSL certificates:
```bash
# Ubuntu/Debian
sudo apt install certbot
# Get a wildcard certificate with DNS challenge
sudo certbot certonly --manual --preferred-challenges dns \
-d yourdomain.org -d '*.yourdomain.org'
```
Alternatively, use the [Certbot Docker image](https://hub.docker.com/r/certbot/certbot/) or a Let's Encrypt companion container.
4. **Update nginx** to listen on 443 with your certificates. Add an SSL server block to `nginx/conf.d/ssl.conf`:
```nginx
server {
listen 443 ssl;
server_name app.yourdomain.org;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://changemaker-v2-admin:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Repeat for api.yourdomain.org, media.yourdomain.org, etc.
# Or use a single server block with $host matching
```
5. **Mount certificates** into the nginx container via `docker-compose.yml`:
```yaml
nginx:
volumes:
- /etc/letsencrypt/live/yourdomain.org:/etc/nginx/ssl:ro
```
6. **Set up auto-renewal** with a cron job or systemd timer:
```bash
0 3 * * * certbot renew --quiet && docker compose restart nginx
```
!!! tip "Traefik alternative"
If you prefer automatic SSL and don't want to manage nginx SSL config manually, consider replacing nginx with [Traefik](https://traefik.io/). Traefik can auto-discover Docker containers and provision Let's Encrypt certificates automatically. This would require adapting the container labels and removing the nginx service.
---
### Option 4: Tailscale / WireGuard (Private Access) { #tailscale }
For deployments that should only be accessible to specific people (not the general public), a mesh VPN like Tailscale or plain WireGuard gives you private networking without exposing anything to the internet.
**Use cases:**
- Internal team deployments
- Development/staging servers
- Access from mobile devices without public exposure
#### Tailscale Setup
1. Install Tailscale on your server and client devices
2. Access services via Tailscale IP (e.g., `http://100.x.x.x:3000`)
3. Optionally use [Tailscale Funnel](https://tailscale.com/kb/1223/funnel/) to selectively expose specific services publicly
#### WireGuard Setup
1. Set up a WireGuard server on your host
2. Connect client devices via WireGuard config
3. Access services via the WireGuard interface IP
!!! note
With private access methods, you may not need subdomain routing at all. Access services directly by port: `http://server-ip:3000` (admin), `http://server-ip:4000` (API), etc.
---
## Production Checklist
Before going live, verify each item:
### Security
- [ ] All placeholder passwords changed (`grep -c "REQUIRED_STRONG" .env` should return `0`)
- [ ] `NODE_ENV=production` set in `.env`
- [ ] `ENCRYPTION_KEY` set and differs from JWT secrets
- [ ] `EMAIL_TEST_MODE=false` (unless you want MailHog in production)
- [ ] `CORS_ORIGINS` includes your production domain
- [ ] Admin password changed after first login
- [ ] Redis password set (`REDIS_PASSWORD`)
### Networking
- [ ] DNS records configured for your domain and subdomains
- [ ] SSL/TLS working (tunnel handles this, or manual certs)
- [ ] All Pangolin resources set to "Not Protected" (if using Pangolin)
- [ ] `curl https://api.yourdomain.org/api/health` returns JSON
### Services
- [ ] Core services running: `docker compose ps` shows `api`, `admin`, `v2-postgres`, `redis`, `nginx` healthy
- [ ] Database migrated: `docker compose exec api npx prisma migrate deploy`
- [ ] Database seeded: `docker compose exec api npx prisma db seed`
- [ ] Admin GUI accessible at `https://app.yourdomain.org`
### Backups
- [ ] Backup script tested: `./scripts/backup.sh`
- [ ] Backup cron job configured (see [Backups](#backups) below)
- [ ] Restore procedure tested at least once
### Monitoring (Optional)
- [ ] Monitoring stack started: `docker compose --profile monitoring up -d`
- [ ] Grafana accessible and dashboards loading
- [ ] Alert rules configured in Alertmanager
---
## Backups
The included backup script dumps PostgreSQL databases, archives uploads, and optionally uploads to S3.
### Running a Backup
```bash
./scripts/backup.sh
```
This creates a timestamped directory under `./backups/` containing:
- `changemaker_v2.sql.gz` &mdash; Main PostgreSQL dump (compressed)
- `listmonk.sql.gz` &mdash; Listmonk database dump (if running)
- `uploads.tar.gz` &mdash; Media uploads archive
- `manifest.json` &mdash; Backup metadata
### Options
```bash
# Upload to S3 (requires AWS CLI + S3_BUCKET env var)
./scripts/backup.sh --s3
# Custom retention (delete local backups older than N days)
./scripts/backup.sh --retention 14
```
### Automated Backups
Add a cron job for daily backups:
```bash
# Edit crontab
crontab -e
# Add daily backup at 3 AM
0 3 * * * /path/to/changemaker.lite/scripts/backup.sh >> /var/log/changemaker-backup.log 2>&1
# With S3 upload
0 3 * * * /path/to/changemaker.lite/scripts/backup.sh --s3 >> /var/log/changemaker-backup.log 2>&1
```
### Restore
```bash
# Restore main database
gunzip -c backups/changemaker-v2-backup-TIMESTAMP/changemaker_v2.sql.gz | \
docker compose exec -T v2-postgres psql -U changemaker changemaker_v2
# Restore Listmonk database
gunzip -c backups/changemaker-v2-backup-TIMESTAMP/listmonk.sql.gz | \
docker compose exec -T listmonk-db psql -U listmonk listmonk
# Restore uploads
tar xzf backups/changemaker-v2-backup-TIMESTAMP/uploads.tar.gz -C ./
```
---
## Monitoring
The monitoring stack runs behind a Docker Compose profile and is not started by default.
### Starting the Monitoring Stack
```bash
docker compose --profile monitoring up -d
```
This starts:
| Service | Port | Purpose |
|---------|------|---------|
| Prometheus | 9090 | Metrics collection and queries |
| Grafana | 3001 | Dashboards and visualization |
| Alertmanager | 9093 | Alert routing and notifications |
| cAdvisor | 8080 | Container resource metrics |
| Node Exporter | 9100 | Host system metrics |
| Redis Exporter | 9121 | Redis metrics |
| Gotify | 8889 | Push notifications |
### Pre-configured Dashboards
Grafana includes 3 auto-provisioned dashboards:
1. **API Overview** &mdash; HTTP request rates, latency, error rates, active sessions
2. **Infrastructure** &mdash; Container CPU/memory, PostgreSQL connections, Redis memory
3. **Campaign Activity** &mdash; Email queue size, campaign sends, response submissions
### Custom Metrics
The API exposes 12 custom Prometheus metrics with the `cm_` prefix:
- `cm_api_uptime_seconds` &mdash; API uptime
- `cm_email_queue_size` &mdash; BullMQ pending emails
- `cm_active_canvass_sessions` &mdash; Active canvassing sessions
- `cm_locations_total` &mdash; Total locations in database
- And more &mdash; see `api/src/utils/metrics.ts`
### Alert Rules
Pre-configured alerts in `configs/prometheus/alerts.yml`:
- API down for more than 5 minutes
- High error rate (>5% of requests returning 5xx)
- Database connection failures
- Redis connection failures
- Email queue backlog
- Disk space warnings
---
## Upgrading
### Pulling Updates
```bash
# Pull latest code
git pull origin v2
# Rebuild and restart containers
docker compose build api admin
docker compose up -d api admin
# Run any new migrations
docker compose exec api npx prisma migrate deploy
```
### Database Migrations
Always run migrations after pulling updates:
```bash
docker compose exec api npx prisma migrate deploy
```
!!! warning "Back up first"
Always run `./scripts/backup.sh` before applying migrations in production. Migrations may alter table structures and are not easily reversible.
---
## Troubleshooting Production Issues
### Pangolin: 302 Redirects Instead of Content
**Symptom:** API returns 302 redirects to the Pangolin authentication page.
**Fix:** In the Pangolin dashboard, edit each resource and set Authentication to **Not Protected**.
### CORS Errors
**Symptom:** Browser console shows CORS errors when accessing the production domain.
**Fix:** Add your production `app.` subdomain to `CORS_ORIGINS` in `.env`, then `docker compose restart api`.
### Newt Won't Connect
Check in order:
1. **Credentials:** Verify `PANGOLIN_NEWT_ID` and `PANGOLIN_NEWT_SECRET` in `.env`
2. **Endpoint:** Confirm `PANGOLIN_ENDPOINT` matches your Pangolin server URL
3. **Logs:** `docker compose logs newt --tail 50`
4. **Nginx running:** Newt depends on nginx &mdash; `docker compose ps nginx`
5. **Network:** Ensure outbound HTTPS is not blocked by your firewall
### Services Unreachable via Tunnel
1. Verify nginx is running: `docker compose ps nginx`
2. Test locally first: `curl http://localhost:4000/api/health`
3. Check nginx logs: `docker compose logs nginx --tail 50`
4. Verify DNS: `dig app.yourdomain.org` should point to your Pangolin server
See [Troubleshooting](../troubleshooting/index.md) for more common issues.

View File

@@ -0,0 +1,40 @@
---
title: Feature Guides
description: Explore Changemaker Lite's campaign, mapping, media, and communication features.
icon: material/star-shooting
---
# Feature Guides
Changemaker Lite bundles advocacy campaigns, geographic mapping, volunteer management, media hosting, and landing pages into a single self-hosted platform.
!!! warning "Under Construction"
Detailed feature guides are being written. This page will link to in-depth documentation for each module.
## Modules Overview
### Influence (Advocacy Campaigns)
- **Campaigns** — create email advocacy campaigns targeting elected representatives
- **Postal Code Lookup** — match constituents to their representatives via postal code
- **Response Wall** — public wall where supporters share their stories, with admin moderation
- **Email Queue** — BullMQ-powered async email delivery with tracking
### Map (Locations & Canvassing)
- **Locations** — manage addresses with multi-provider geocoding and CSV import/export
- **Cuts** — define polygon regions for organizing canvassing areas
- **Shifts** — schedule volunteer shifts with public signup
- **Canvassing** — GPS-tracked door-to-door canvassing with visit recording
### Media Manager
- **Video Library** — upload, organize, and manage campaign videos
- **Public Gallery** — share videos publicly with analytics tracking
- **Scheduled Publishing** — automate video publish/unpublish with timezone support
### Pages & Templates
- **Landing Pages** — drag-and-drop page builder (GrapesJS)
- **Email Templates** — reusable email templates with variable substitution
- **MkDocs Export** — export pages to the documentation site

View File

@@ -0,0 +1,503 @@
---
title: Environment Variables
description: Complete reference for every .env variable in Changemaker Lite.
icon: material/file-cog
---
# Environment Variables
Changemaker Lite uses a single `.env` file at the project root to configure all services. Copy the example file to get started:
```bash
cp .env.example .env
```
!!! danger "Security Essentials"
- **Change every** `REQUIRED_STRONG_PASSWORD_CHANGE_THIS` value before starting services
- **Generate secrets** with `openssl rand -hex 32` (or `-hex 16` where noted)
- **Never** commit `.env` to version control
- Use unique values for each secret &mdash; do not reuse JWT secrets as encryption keys
---
## Quick Reference
Variables are grouped by service. Each table marks whether a variable is **required** for a basic deployment or **optional** (has a sensible default or only needed for specific features).
| Symbol | Meaning |
|--------|---------|
| :material-alert-circle:{ .text-red } | Must be set before first run |
| :material-tune-variant: | Has a working default; change for production |
| :material-flask: | Feature flag &mdash; opt-in |
---
## General
| Variable | Default | Description |
|----------|---------|-------------|
| `NODE_ENV` | `development` | Set to `production` for production deployments. Controls logging, error detail, and security checks. |
| `DOMAIN` | `cmlite.org` | Root domain. Used for nginx subdomain routing (`app.DOMAIN`, `api.DOMAIN`, etc.). The root domain serves the MkDocs documentation site; all application routes live under `app.DOMAIN`. |
| `USER_ID` | `1000` | UID for container file ownership. Match your host user's UID (`id -u`). |
| `GROUP_ID` | `1000` | GID for container file ownership. Match your host user's GID (`id -g`). |
| `DOCKER_GROUP_ID` | `984` | GID of the `docker` group on the host. Needed for containers that access the Docker socket. Find with `getent group docker`. |
---
## PostgreSQL (Main Database) :material-alert-circle:{ .text-red }
The primary database for both the Express API and the Fastify Media API (shared).
| Variable | Default | Description |
|----------|---------|-------------|
| `V2_POSTGRES_USER` | `changemaker` | Database username. |
| `V2_POSTGRES_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } **Must change.** Database password. |
| `V2_POSTGRES_DB` | `changemaker_v2` | Database name. |
| `V2_POSTGRES_PORT` | `5433` | Host port mapping. The container listens on `5432` internally. |
!!! tip "Connection string"
The `DATABASE_URL` is constructed automatically inside Docker. If running locally, set:
```
DATABASE_URL=postgresql://changemaker:YOUR_PASSWORD@localhost:5433/changemaker_v2
```
---
## JWT Authentication :material-alert-circle:{ .text-red }
| Variable | Default | Description |
|----------|---------|-------------|
| `JWT_ACCESS_SECRET` | &mdash; | :material-alert-circle:{ .text-red } Secret for signing access tokens. Generate with `openssl rand -hex 32`. |
| `JWT_REFRESH_SECRET` | &mdash; | :material-alert-circle:{ .text-red } Secret for signing refresh tokens. **Must differ** from the access secret. |
| `JWT_ACCESS_EXPIRY` | `15m` | Access token lifetime. Short-lived by design. |
| `JWT_REFRESH_EXPIRY` | `7d` | Refresh token lifetime. Tokens are rotated atomically on each refresh. |
---
## Encryption Key :material-alert-circle:{ .text-red }
| Variable | Default | Description |
|----------|---------|-------------|
| `ENCRYPTION_KEY` | &mdash; | :material-alert-circle:{ .text-red } AES key for encrypting secrets stored in the database (SMTP passwords, API keys, etc.). Generate with `openssl rand -hex 32`. **Must not** reuse a JWT secret. Required in production (`NODE_ENV=production`). |
---
## Initial Admin Account :material-alert-circle:{ .text-red }
These credentials create the first super-admin user during database seeding (`npx prisma db seed`).
| Variable | Default | Description |
|----------|---------|-------------|
| `INITIAL_ADMIN_EMAIL` | `admin@cmlite.org` | Email address for the initial admin. |
| `INITIAL_ADMIN_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } **Must change.** Must be 12+ characters with uppercase, lowercase, and a digit. Change this password after first login. |
---
## API Server
| Variable | Default | Description |
|----------|---------|-------------|
| `API_PORT` | `4000` | Host port for the Express API. |
| `API_URL` | `http://localhost:4000` | Public URL of the API. Used for generating links in emails and QR codes. |
| `CORS_ORIGINS` | `http://localhost:3000,http://localhost` | Comma-separated list of allowed CORS origins. **Add your production domain** (e.g., `https://app.yourdomain.org`) for production. |
!!! warning "Production CORS"
If you deploy behind a tunnel (Pangolin, Cloudflare) and API requests fail with CORS errors, add your production `app.` subdomain here:
```
CORS_ORIGINS=https://app.betteredmonton.org,http://localhost:3000,http://localhost
```
Then restart the API: `docker compose restart api`
---
## Admin GUI
| Variable | Default | Description |
|----------|---------|-------------|
| `ADMIN_PORT` | `3000` | Host port for the React admin dashboard. |
| `ADMIN_URL` | `http://localhost:3000` | Public URL of the admin GUI. |
---
## Nginx Reverse Proxy
| Variable | Default | Description |
|----------|---------|-------------|
| `NGINX_HTTP_PORT` | `80` | HTTP port. All subdomains route through nginx. |
| `NGINX_HTTPS_PORT` | `443` | HTTPS port. SSL is typically handled by the tunnel provider (Pangolin/Cloudflare). |
---
## Redis :material-alert-circle:{ .text-red }
Shared by rate limiting, BullMQ job queues, geocoding cache, and session data.
| Variable | Default | Description |
|----------|---------|-------------|
| `REDIS_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } **Must change.** Redis requires authentication. |
| `REDIS_URL` | `redis://:${REDIS_PASSWORD}@redis-changemaker:6379` | Full connection URL. Uses the password variable automatically. |
---
## Email / SMTP :material-tune-variant:
| Variable | Default | Description |
|----------|---------|-------------|
| `SMTP_HOST` | `mailhog-changemaker` | SMTP server. Default points to the MailHog dev container. |
| `SMTP_PORT` | `1025` | SMTP port. `1025` for MailHog, `587` for most production SMTP. |
| `SMTP_USER` | *(empty)* | SMTP username. Not needed for MailHog. |
| `SMTP_PASS` | *(empty)* | SMTP password. |
| `SMTP_FROM` | `noreply@cmlite.org` | "From" address on outgoing emails. |
| `SMTP_FROM_NAME` | `Changemaker Lite` | Display name for the "From" header. |
| `EMAIL_TEST_MODE` | `true` | When `true`, all emails go to MailHog instead of real SMTP. **Set to `false` in production.** |
| `TEST_EMAIL_RECIPIENT` | `admin@cmlite.org` | Catch-all recipient when test mode is on. |
!!! info "Development email"
With `EMAIL_TEST_MODE=true`, all outgoing email is captured in MailHog at `http://localhost:8025`. No real emails are sent.
---
## Listmonk (Newsletters) :material-flask:
Listmonk handles newsletter/marketing campaigns. Sync with the main platform is opt-in.
| Variable | Default | Description |
|----------|---------|-------------|
| `LISTMONK_PORT` | `9001` | Listmonk web UI port. |
| `LISTMONK_DB_PORT` | `5432` | Listmonk's own PostgreSQL port (separate from the main DB). |
| `LISTMONK_DB_USER` | `listmonk` | Listmonk database user. |
| `LISTMONK_DB_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } Listmonk database password. |
| `LISTMONK_DB_NAME` | `listmonk` | Listmonk database name. |
| `LISTMONK_WEB_ADMIN_USER` | `admin` | Login for the Listmonk web dashboard. |
| `LISTMONK_WEB_ADMIN_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } Password for the Listmonk web dashboard. |
| `LISTMONK_API_USER` | `v2-api` | API user for programmatic access (auto-created by init container). |
| `LISTMONK_API_TOKEN` | &mdash; | Token for API user. Generate with `openssl rand -hex 16`. |
| `LISTMONK_ADMIN_USER` | `v2-api` | Same as `LISTMONK_API_USER` (used by the sync service). |
| `LISTMONK_ADMIN_PASSWORD` | &mdash; | Same as `LISTMONK_API_TOKEN`. |
| `LISTMONK_SYNC_ENABLED` | `false` | :material-flask: Set to `true` to sync participants/locations/users to Listmonk lists. |
| `LISTMONK_PROXY_PORT` | `9002` | Nginx proxy port for Listmonk. |
??? example "Listmonk SMTP settings"
Listmonk has its own SMTP configuration, separate from the main platform's:
| Variable | Default | Description |
|----------|---------|-------------|
| `LISTMONK_SMTP_HOST` | `mailhog-changemaker` | SMTP host for Listmonk. |
| `LISTMONK_SMTP_PORT` | `1025` | SMTP port. |
| `LISTMONK_SMTP_USER` | *(empty)* | SMTP username. |
| `LISTMONK_SMTP_PASSWORD` | *(empty)* | SMTP password. |
| `LISTMONK_SMTP_TLS_TYPE` | `none` | TLS mode: `none`, `STARTTLS`, or `TLS`. |
| `LISTMONK_SMTP_FROM` | `Changemaker Lite <noreply@cmlite.org>` | From address for newsletters. |
---
## Represent API (Canadian Electoral Data)
| Variable | Default | Description |
|----------|---------|-------------|
| `REPRESENT_API_URL` | `https://represent.opennorth.ca` | OpenNorth Represent API endpoint. Used for postal code &rarr; representative lookups. No API key required. |
---
## NocoDB (Data Browser) :material-tune-variant:
Read-only database browser. Useful for inspecting data without SQL.
| Variable | Default | Description |
|----------|---------|-------------|
| `NOCODB_V2_PORT` / `NOCODB_PORT` | `8091` | Host port for the NocoDB web UI. |
| `NOCODB_URL` | `http://changemaker-v2-nocodb:8080` | Internal Docker URL. |
| `NC_ADMIN_EMAIL` | `admin@cmlite.org` | NocoDB admin email. |
| `NC_ADMIN_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } NocoDB admin password. |
---
## Media Manager :material-flask:
Video library with upload, analytics, scheduling, and a public gallery.
| Variable | Default | Description |
|----------|---------|-------------|
| `ENABLE_MEDIA_FEATURES` | `false` | :material-flask: Set to `true` to enable the media system. |
| `MEDIA_API_PORT` | `4100` | Fastify media API port. |
| `MEDIA_API_PUBLIC_URL` | `http://media-api:4100` | Internal URL for the media API container. |
| `MEDIA_ROOT` | `/media/library` | Path to the video library inside the container. |
| `MEDIA_UPLOADS` | `/media/uploads` | Path for upload processing. |
| `MAX_UPLOAD_SIZE_GB` | `10` | Maximum single-file upload size in gigabytes. |
| `VIDEO_PLAYER_DEBUG` | `false` | Enable verbose video player logging. |
??? example "Analytics & scheduling settings"
| Variable | Default | Description |
|----------|---------|-------------|
| `VIDEO_ANALYTICS_RETENTION_DAYS` | `90` | Days to retain analytics data. GDPR-compliant with IP hashing. |
| `VIDEO_ANALYTICS_IP_HASHING_ENABLED` | `true` | Hash viewer IPs for privacy. |
| `VIDEO_SCHEDULE_DEFAULT_TIMEZONE` | `UTC` | Default timezone for scheduled publishing. |
| `VIDEO_SCHEDULE_NOTIFICATION_ENABLED` | `true` | Notify on scheduled publish/unpublish. |
| `VIDEO_PREVIEW_LINK_EXPIRY_HOURS` | `24` | Preview link JWT expiry (hours). |
---
## Gitea (Git Hosting) :material-tune-variant:
Self-hosted Git repository. Optional service.
| Variable | Default | Description |
|----------|---------|-------------|
| `GITEA_PORT` / `GITEA_WEB_PORT` | `3030` | Gitea web UI port. |
| `GITEA_SSH_PORT` | `2222` | Gitea SSH port for git operations. |
| `GITEA_DB_TYPE` | `mysql` | Database type (Gitea uses its own MySQL). |
| `GITEA_DB_HOST` | `gitea-db:3306` | Internal database host. |
| `GITEA_DB_NAME` | `gitea` | Database name. |
| `GITEA_DB_USER` | `gitea` | Database user. |
| `GITEA_DB_PASSWD` | &mdash; | :material-alert-circle:{ .text-red } Gitea database password. |
| `GITEA_DB_ROOT_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } MySQL root password for Gitea. |
| `GITEA_ROOT_URL` | `https://git.cmlite.org` | Public-facing URL for Gitea. |
| `GITEA_DOMAIN` | `git.cmlite.org` | Domain used in git clone URLs. |
---
## n8n (Workflow Automation) :material-tune-variant:
| Variable | Default | Description |
|----------|---------|-------------|
| `N8N_PORT` | `5678` | n8n web UI port. |
| `N8N_HOST` | `n8n.cmlite.org` | Public hostname for n8n. |
| `N8N_ENCRYPTION_KEY` | &mdash; | :material-alert-circle:{ .text-red } Encryption key for n8n credentials storage. |
| `N8N_USER_EMAIL` | `admin@example.com` | Initial n8n admin email. |
| `N8N_USER_PASSWORD` | &mdash; | :material-alert-circle:{ .text-red } Initial n8n admin password. |
| `GENERIC_TIMEZONE` | `UTC` | Timezone for n8n cron triggers. |
---
## MkDocs (Documentation)
| Variable | Default | Description |
|----------|---------|-------------|
| `MKDOCS_PORT` | `4003` | MkDocs dev server port (live preview). |
| `MKDOCS_SITE_SERVER_PORT` | `4001` | MkDocs static site server port. |
| `BASE_DOMAIN` | `https://cmlite.org` | Base URL for generated documentation links. |
| `MKDOCS_PREVIEW_URL` | `http://mkdocs:8000` | Internal container URL. |
| `MKDOCS_DOCS_PATH` | `/mkdocs/docs` | Documentation source directory inside the container. |
---
## Code Server (Web IDE)
| Variable | Default | Description |
|----------|---------|-------------|
| `CODE_SERVER_PORT` | `8888` | Code Server web UI port. |
| `CODE_SERVER_URL` | `http://code-server:8080` | Internal container URL. |
| `USER_NAME` | `coder` | User account inside the Code Server container. |
---
## Homepage (Service Dashboard)
| Variable | Default | Description |
|----------|---------|-------------|
| `HOMEPAGE_PORT` | `3010` | Homepage web UI port. |
| `HOMEPAGE_EMBED_PORT` | `8887` | Port for iframe embedding in admin. |
| `HOMEPAGE_VAR_BASE_URL` | `http://localhost` | Base URL used in Homepage service links. |
---
## Mini QR (QR Code Generator)
| Variable | Default | Description |
|----------|---------|-------------|
| `MINI_QR_PORT` | `8089` | Mini QR direct access port. |
| `MINI_QR_URL` | `http://mini-qr:8080` | Internal container URL. |
| `MINI_QR_EMBED_PORT` | `8885` | Port for iframe embedding (walk sheets, cut exports). |
---
## Excalidraw (Whiteboard)
| Variable | Default | Description |
|----------|---------|-------------|
| `EXCALIDRAW_PORT` | `8090` | Excalidraw web UI port. |
| `EXCALIDRAW_URL` | `http://excalidraw-changemaker:80` | Internal container URL. |
| `EXCALIDRAW_EMBED_PORT` | `8886` | Port for iframe embedding. |
| `EXCALIDRAW_WS_URL` | `wss://draw.cmlite.org` | WebSocket URL for real-time collaboration. |
---
## MailHog (Development Email)
| Variable | Default | Description |
|----------|---------|-------------|
| `MAILHOG_SMTP_PORT` | `1025` | SMTP port for capturing emails. |
| `MAILHOG_WEB_PORT` | `8025` | Web UI to view captured emails. |
---
## NAR (National Address Register) :material-tune-variant:
Canadian address data import for geographic canvassing.
| Variable | Default | Description |
|----------|---------|-------------|
| `NAR_DATA_DIR` | `/data` | Path to extracted NAR data inside the container. Expects `YYYYMM/Addresses/` and `YYYYMM/Locations/` subdirectories. Mount via `./data:/data:ro` in Docker Compose. |
Download NAR data from [Statistics Canada](https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm).
---
## Geocoding :material-tune-variant:
Multi-provider geocoding for address resolution. Works out of the box with free providers; optional paid providers improve accuracy.
| Variable | Default | Description |
|----------|---------|-------------|
| `MAPBOX_API_KEY` | *(empty)* | Mapbox API key for improved geocoding accuracy. Free tier: 100k requests/month. [Sign up](https://www.mapbox.com/pricing). |
| `GEOCODING_RATE_LIMIT_MS` | `1100` | Delay between requests to free providers (ms). Respects rate limits. |
| `GEOCODING_CACHE_ENABLED` | `true` | Enable Redis-backed geocoding cache. |
| `GEOCODING_CACHE_TTL_HOURS` | `24` | Cache lifetime in hours. |
| `GOOGLE_MAPS_API_KEY` | *(empty)* | Google Maps API key. Most accurate but $0.005/request after free tier. |
| `GOOGLE_MAPS_ENABLED` | `false` | Enable Google Maps as a geocoding provider. |
| `GEOCODING_PARALLEL_ENABLED` | `true` | Enable parallel geocoding for bulk imports (~10x speedup). |
| `GEOCODING_BATCH_SIZE` | `10` | Number of concurrent geocoding requests during bulk operations. |
| `BULK_GEOCODE_ENABLED` | `true` | Enable bulk re-geocoding from the admin UI. |
| `BULK_GEOCODE_MAX_BATCH` | `5000` | Maximum locations per bulk geocoding run. |
---
## Overpass / Area Import :material-tune-variant:
OpenStreetMap data import for map enrichment.
| Variable | Default | Description |
|----------|---------|-------------|
| `OVERPASS_API_URL` | `https://overpass-api.de/api/interpreter` | Overpass API endpoint. Use a private instance for heavy usage. |
| `OVERPASS_MIN_DELAY_MS` | `30000` | Minimum delay between requests (ms). The public API requires 30 seconds. |
| `AREA_IMPORT_MAX_GRID_POINTS` | `500` | Maximum reverse-geocode grid points per area import. |
---
## Pangolin Tunnel :material-tune-variant:
Expose services to the internet without port forwarding, using a self-hosted [Pangolin](https://github.com/fosrl/pangolin) instance.
| Variable | Default | Description |
|----------|---------|-------------|
| `PANGOLIN_API_URL` | `https://api.bnkserve.org/v1` | Pangolin server API endpoint. |
| `PANGOLIN_API_KEY` | *(empty)* | API key for Pangolin management. |
| `PANGOLIN_ORG_ID` | *(empty)* | Organization ID in Pangolin. |
| `PANGOLIN_SITE_ID` | *(empty)* | Site ID (populated after setup via admin GUI). |
| `PANGOLIN_ENDPOINT` | `https://pangolin.bnkserve.org` | Pangolin tunnel endpoint. |
| `PANGOLIN_NEWT_ID` | *(empty)* | Newt client ID (populated after setup). |
| `PANGOLIN_NEWT_SECRET` | *(empty)* | Newt client secret (populated after setup). |
!!! tip "Setup flow"
Configure the tunnel from **Admin &rarr; Settings &rarr; Pangolin**. The setup wizard walks you through creating a site, copying credentials, and connecting the Newt container. See [Deployment](../deployment/index.md) for the full guide.
---
## Monitoring :material-flask:
These services are behind the `monitoring` Docker Compose profile. Start them with:
```bash
docker compose --profile monitoring up -d
```
| Variable | Default | Description |
|----------|---------|-------------|
| `PROMETHEUS_PORT` | `9090` | Prometheus web UI / query port. |
| `GRAFANA_PORT` | `3001` | Grafana dashboard port. |
| `GRAFANA_ADMIN_PASSWORD` | `admin` | :material-tune-variant: Change in production. |
| `GRAFANA_ROOT_URL` | `http://localhost:3001` | Public URL for Grafana (used in links). |
| `CADVISOR_PORT` | `8080` | cAdvisor container metrics port. |
| `NODE_EXPORTER_PORT` | `9100` | Prometheus node exporter port. |
| `REDIS_EXPORTER_PORT` | `9121` | Redis metrics exporter port. |
| `ALERTMANAGER_PORT` | `9093` | Alertmanager web UI port. |
| `GOTIFY_PORT` | `8889` | Gotify push notification port. |
| `GOTIFY_ADMIN_USER` | `admin` | Gotify admin username. |
| `GOTIFY_ADMIN_PASSWORD` | `admin` | :material-tune-variant: Change in production. |
---
## Generating Secrets
Use these commands to generate all required secrets at once:
```bash
# JWT secrets (two separate values)
echo "JWT_ACCESS_SECRET=$(openssl rand -hex 32)"
echo "JWT_REFRESH_SECRET=$(openssl rand -hex 32)"
# Encryption key (must differ from JWT secrets)
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"
# Database and Redis passwords
echo "V2_POSTGRES_PASSWORD=$(openssl rand -hex 24)"
echo "REDIS_PASSWORD=$(openssl rand -hex 24)"
# Listmonk
echo "LISTMONK_DB_PASSWORD=$(openssl rand -hex 24)"
echo "LISTMONK_WEB_ADMIN_PASSWORD=$(openssl rand -hex 16)"
LISTMONK_TOKEN=$(openssl rand -hex 16)
echo "LISTMONK_API_TOKEN=$LISTMONK_TOKEN"
echo "LISTMONK_ADMIN_PASSWORD=$LISTMONK_TOKEN"
# Supporting services
echo "GITEA_DB_PASSWD=$(openssl rand -hex 24)"
echo "GITEA_DB_ROOT_PASSWORD=$(openssl rand -hex 24)"
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)"
echo "N8N_USER_PASSWORD=$(openssl rand -hex 16)"
echo "NC_ADMIN_PASSWORD=$(openssl rand -hex 16)"
echo "INITIAL_ADMIN_PASSWORD=$(openssl rand -base64 18)"
```
!!! tip
Copy the output and paste the values into your `.env` file. The `INITIAL_ADMIN_PASSWORD` uses base64 encoding to ensure it contains uppercase, lowercase, and digits (meeting the password policy).
---
## Minimal vs Full Deployment
=== "Minimal (Core Only)"
For a basic deployment with campaigns, map, and admin:
```bash title="Required variables"
V2_POSTGRES_PASSWORD=...
REDIS_PASSWORD=...
JWT_ACCESS_SECRET=...
JWT_REFRESH_SECRET=...
ENCRYPTION_KEY=...
INITIAL_ADMIN_PASSWORD=...
```
```bash title="Start services"
docker compose up -d v2-postgres redis api admin
```
=== "Full Stack"
For the complete platform including media, newsletters, monitoring, and all services:
```bash title="Additional variables needed"
# Everything above, plus:
ENABLE_MEDIA_FEATURES=true
LISTMONK_SYNC_ENABLED=true
LISTMONK_DB_PASSWORD=...
LISTMONK_WEB_ADMIN_PASSWORD=...
LISTMONK_API_TOKEN=...
NC_ADMIN_PASSWORD=...
GITEA_DB_PASSWD=...
GITEA_DB_ROOT_PASSWORD=...
N8N_ENCRYPTION_KEY=...
N8N_USER_PASSWORD=...
EMAIL_TEST_MODE=false
SMTP_HOST=smtp.your-provider.com
SMTP_PORT=587
SMTP_USER=you@example.com
SMTP_PASS=your-smtp-password
```
```bash title="Start services"
docker compose up -d
docker compose --profile monitoring up -d
```

View File

@@ -0,0 +1,146 @@
---
title: Getting Started
description: Install and configure Changemaker Lite from scratch.
icon: material/rocket-launch
---
# Getting Started
This guide walks you through installing Changemaker Lite, running your first deployment, and logging into the admin dashboard.
## Prerequisites
- **Docker** 24+ and **Docker Compose** v2
- **OpenSSL** (for secret generation)
- A Linux server (Ubuntu 22.04+ recommended) or macOS for development
- At least 2 GB RAM and 10 GB disk space
- A domain name (optional, but recommended for production)
## Installation
### 1. Clone the Repository
```bash
git clone https://gitea.bnkops.com/admin/changemaker.lite
cd changemaker.lite
git checkout v2
```
### 2. Run the Configuration Wizard
The fastest way to get a working `.env` file is the interactive configuration wizard:
```bash
bash config.sh
```
The wizard walks you through each step:
| Step | What it does |
|------|-------------|
| **Prerequisites check** | Verifies Docker, Docker Compose, and OpenSSL are installed |
| **Domain** | Sets your root domain and updates all subdomain references (nginx, Gitea, n8n, MkDocs, etc.) |
| **Admin credentials** | Prompts for the initial super-admin email and password (enforces 12+ chars, uppercase, lowercase, digit) |
| **Secret generation** | Auto-generates 16 unique secrets &mdash; JWT keys, encryption key, database passwords, Redis password, API tokens |
| **SMTP** | Optionally configures production SMTP (defaults to MailHog for development) |
| **Feature flags** | Enable/disable Media Manager and Listmonk newsletter sync |
| **Pangolin tunnel** | Optionally configures tunnel credentials for public access |
| **CORS** | Auto-sets allowed origins based on your domain |
| **Homepage** | Generates `configs/homepage/services.yaml` with all service links for your domain |
| **Permissions** | Creates required directories and sets container-friendly permissions |
After completion you'll have a fully populated `.env` with no placeholder passwords remaining.
!!! tip "Already have a `.env`?"
If a `.env` file exists, the wizard offers to back it up before creating a fresh one, or update values in place.
??? example "What the wizard looks like"
```
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
V2 Configuration Wizard
[INFO] This wizard will create your .env file, generate secure secrets,
[INFO] and prepare your system to run the full Changemaker Lite stack.
```
### 3. Manual Setup (Alternative)
If you prefer to configure things by hand:
```bash
cp .env.example .env
```
Then edit `.env` and at minimum set these values:
```bash
V2_POSTGRES_PASSWORD=<strong password>
REDIS_PASSWORD=<strong password>
JWT_ACCESS_SECRET=<openssl rand -hex 32>
JWT_REFRESH_SECRET=<openssl rand -hex 32>
ENCRYPTION_KEY=<openssl rand -hex 32>
INITIAL_ADMIN_PASSWORD=<12+ chars, mixed case + digit>
```
See [Environment Variables](environment-variables.md) for every available option.
### 4. Start Services
```bash
# Start core services
docker compose up -d v2-postgres redis api admin
# Run database migrations and seed the initial admin account
docker compose exec api npx prisma migrate deploy
docker compose exec api npx prisma db seed
```
### 5. Log In
Open **http://localhost:3000** and sign in with the admin email and password you configured.
!!! warning "Change your password"
If you used the wizard's generated password, change it immediately from the admin dashboard.
## Optional Services
Once the core is running, add more services as needed:
```bash
# Reverse proxy (required for subdomain routing)
docker compose up -d nginx
# Video library
docker compose up -d media-api
# Newsletters
docker compose up -d listmonk-app
# Service dashboard
docker compose up -d homepage
# All services at once
docker compose up -d
# Monitoring stack (Prometheus, Grafana, Alertmanager)
docker compose --profile monitoring up -d
```
## Next Steps
- [Environment Variables](environment-variables.md) — complete `.env` reference with every configurable option
- [Feature Guides](../features/index.md) — explore campaigns, map, and media
- [Deployment](../deployment/index.md) — production setup with SSL and tunneling
- [Architecture](../architecture/index.md) — understand the system design

146
mkdocs/docs/docs/index.md Normal file
View File

@@ -0,0 +1,146 @@
---
title: Documentation
description: Changemaker Lite documentation hub — guides for users, admins, and operators.
icon: material/book-open-variant
---
# Documentation
Welcome to the Changemaker Lite documentation. Whether you're a campaign volunteer, an admin managing operations, or a sysadmin deploying the platform — start here.
---
## Use the Platform
<div class="grid cards" markdown>
- :material-rocket-launch:{ .lg .middle } **Getting Started**
---
Install Changemaker Lite, create your first admin account, and explore the dashboard.
[:octicons-arrow-right-24: Getting Started](getting-started/index.md)
- :material-star-shooting:{ .lg .middle } **Feature Guides**
---
Campaigns, email advocacy, response walls, map locations, landing pages, and media.
[:octicons-arrow-right-24: Feature Guides](features/index.md)
- :material-shield-account:{ .lg .middle } **Administration**
---
User management, roles and permissions, site settings, email templates, and newsletters.
[:octicons-arrow-right-24: Administration](admin/index.md)
- :material-walk:{ .lg .middle } **Volunteer Guide**
---
Sign up for shifts, use the canvassing map, record visits, and track your activity.
[:octicons-arrow-right-24: Volunteer Guide](volunteer/index.md)
</div>
## Deploy & Operate
<div class="grid cards" markdown>
- :material-docker:{ .lg .middle } **Deployment**
---
Docker Compose setup, environment variables, SSL/TLS, backups, and production checklist.
[:octicons-arrow-right-24: Deployment](deployment/index.md)
- :material-sitemap:{ .lg .middle } **Architecture**
---
Dual API design, database schema, authentication flow, and system diagram.
[:octicons-arrow-right-24: Architecture](architecture/index.md)
- :material-server-network:{ .lg .middle } **Services**
---
Nginx routing, Redis, PostgreSQL, Listmonk, MkDocs, Gitea, NocoDB, and more.
[:octicons-arrow-right-24: Services](services/index.md)
- :material-chart-line:{ .lg .middle } **Monitoring**
---
Prometheus metrics, Grafana dashboards, Alertmanager rules, and health checks.
[:octicons-arrow-right-24: Monitoring](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
</div>
## Reference
<div class="grid cards" markdown>
- :material-api:{ .lg .middle } **API Reference**
---
REST endpoints for auth, campaigns, locations, shifts, media, and more.
[:octicons-arrow-right-24: API Reference](api/index.md)
- :material-bug:{ .lg .middle } **Troubleshooting**
---
Common errors, CORS issues, database problems, tunnel debugging, and FAQ.
[:octicons-arrow-right-24: Troubleshooting](troubleshooting/index.md)
- :material-lock-check:{ .lg .middle } **Security**
---
Password policy, rate limiting, token rotation, encryption, and audit report.
[:octicons-arrow-right-24: Security](deployment/index.md){ .md-button .md-button--secondary } *See Deployment*
- :material-source-pull:{ .lg .middle } **Contributing**
---
Development setup, code style, git workflow, and pull request guidelines.
[:octicons-arrow-right-24: Contributing](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
</div>
---
## Platform at a Glance
| Component | Technology | Purpose |
|-----------|-----------|---------|
| **Main API** | Express.js + Prisma | Auth, campaigns, map, shifts, pages, email |
| **Media API** | Fastify + Prisma | Video library, analytics, upload, scheduling |
| **Admin GUI** | React + Ant Design + Zustand | Dashboard for admins and organizers |
| **Database** | PostgreSQL 16 | Single shared database for both APIs |
| **Cache** | Redis | Rate limiting, BullMQ jobs, geocoding queue |
| **Proxy** | Nginx | Subdomain routing, security headers, SSL |
| **Tunnel** | Pangolin + Newt | Expose services without port forwarding |
| **Monitoring** | Prometheus + Grafana | Metrics, dashboards, alerts |
!!! tip "New here?"
Start with the [Getting Started](getting-started/index.md) guide to have the platform running in under 30 minutes.
!!! info "Looking for the source?"
Changemaker Lite is 100% open source. Browse the code on [Gitea](https://gitea.bnkops.com/admin/changemaker.lite).

View File

@@ -0,0 +1,361 @@
---
title: Services
description: All services that make up the Changemaker Lite platform — configuration, ports, and links to upstream docs.
icon: material/server-network
---
# Services
Changemaker Lite orchestrates 20+ services via Docker Compose. This page is your map to every service: what it does, how to reach it, and where to find its upstream documentation.
---
## Core Platform
The essential services that power the application.
<div class="grid cards" markdown>
- :material-api:{ .lg .middle } **Express API**
---
Main V2 API server. Handles authentication, campaigns, map, shifts, pages, email, and all business logic. Prisma ORM with PostgreSQL.
**Port:** `4000` &middot; **Container:** `changemaker-v2-api`
[:octicons-arrow-right-24: API Reference](../api/index.md)
- :material-video:{ .lg .middle } **Fastify Media API**
---
Video library server. Upload, metadata extraction (FFprobe), analytics, scheduled publishing, and public gallery. Shares the same PostgreSQL database.
**Port:** `4100` &middot; **Container:** `changemaker-media-api`
[:octicons-arrow-right-24: Media Guide](../features/index.md)
- :material-react:{ .lg .middle } **Admin GUI**
---
React single-page application (Vite + Ant Design + Zustand). Serves the admin dashboard, public campaign pages, volunteer portal, and media gallery &mdash; all from one build.
**Port:** `3000` &middot; **Container:** `changemaker-v2-admin`
[:octicons-arrow-right-24: Feature Guides](../features/index.md)
- :material-database:{ .lg .middle } **PostgreSQL 16**
---
Primary database shared by both APIs. Managed by Prisma migrations. Contains 30+ tables covering users, campaigns, locations, shifts, media, and more.
**Port:** `5433` (host) / `5432` (container) &middot; **Container:** `changemaker-v2-postgres`
[:octicons-arrow-right-24: PostgreSQL Docs](https://www.postgresql.org/docs/16/){ target="_blank" }
- :material-memory:{ .lg .middle } **Redis**
---
In-memory store for rate limiting, BullMQ job queues (email, video scheduling), geocoding cache, and session data. Requires authentication.
**Port:** `6379` &middot; **Container:** `redis-changemaker`
[:octicons-arrow-right-24: Redis Docs](https://redis.io/docs/){ target="_blank" }
- :material-web:{ .lg .middle } **Nginx**
---
Reverse proxy handling all subdomain routing (`app.`, `api.`, `media.`, `docs.`, etc.). Includes security headers (HSTS, CSP, Permissions-Policy) and WebSocket support.
**Port:** `80` / `443` &middot; **Container:** `changemaker-v2-nginx`
[:octicons-arrow-right-24: Nginx Docs](https://nginx.org/en/docs/){ target="_blank" }
</div>
---
## Communication & Email
<div class="grid cards" markdown>
- :material-email-newsletter:{ .lg .middle } **Listmonk**
---
Self-hosted newsletter and mailing list manager. Drop-in replacement for Mailchimp. Opt-in sync with the main platform imports participants, locations, and users as subscriber lists.
**Port:** `9001` &middot; **Container:** `listmonk-app` &middot; **Subdomain:** `listmonk.DOMAIN`
[:octicons-arrow-right-24: Listmonk Docs](https://listmonk.app/docs/){ target="_blank" }
- :material-email-check:{ .lg .middle } **MailHog**
---
Email capture for development. All outgoing email is intercepted and displayed in a web UI when `EMAIL_TEST_MODE=true`. No real emails are sent.
**Port:** `8025` (web) / `1025` (SMTP) &middot; **Container:** `mailhog-changemaker` &middot; **Subdomain:** `mail.DOMAIN`
[:octicons-arrow-right-24: MailHog GitHub](https://github.com/mailhog/MailHog){ target="_blank" }
</div>
---
## Content & Editing
<div class="grid cards" markdown>
- :material-book-open-page-variant:{ .lg .middle } **MkDocs**
---
Material-themed documentation site with full-text search, blog, social cards, and Jinja2 template overrides. Two containers: live preview (dev) and static site (production).
**Port:** `4003` (dev) / `4001` (static) &middot; **Container:** `mkdocs-changemaker` &middot; **Subdomain:** `docs.DOMAIN`
[:octicons-arrow-right-24: MkDocs Material](https://squidfunk.github.io/mkdocs-material/){ target="_blank" }
- :material-microsoft-visual-studio-code:{ .lg .middle } **Code Server**
---
Full VS Code in the browser. Edit configuration files, templates, and documentation from anywhere without SSH. Supports extensions.
**Port:** `8888` &middot; **Container:** `code-server-changemaker` &middot; **Subdomain:** `code.DOMAIN`
[:octicons-arrow-right-24: Code Server Docs](https://coder.com/docs/code-server){ target="_blank" }
</div>
---
## Data & Automation
<div class="grid cards" markdown>
- :material-table:{ .lg .middle } **NocoDB**
---
Airtable-alternative database browser. Provides a spreadsheet-like interface to browse, filter, sort, and export campaign data. Read-only access to the main database.
**Port:** `8091` &middot; **Container:** `changemaker-v2-nocodb` &middot; **Subdomain:** `db.DOMAIN`
[:octicons-arrow-right-24: NocoDB Docs](https://docs.nocodb.com/){ target="_blank" }
- :material-robot:{ .lg .middle } **n8n**
---
Visual workflow automation platform. Connect APIs, trigger actions on events, schedule tasks, and build custom integrations &mdash; all without code. 400+ built-in integrations.
**Port:** `5678` &middot; **Container:** `n8n-changemaker` &middot; **Subdomain:** `n8n.DOMAIN`
[:octicons-arrow-right-24: n8n Docs](https://docs.n8n.io/){ target="_blank" }
- :material-git:{ .lg .middle } **Gitea**
---
Self-hosted Git repository hosting. Version control for campaign code, configuration, templates, and documentation. Includes issues, pull requests, and CI/CD.
**Port:** `3030` (web) / `2222` (SSH) &middot; **Container:** `gitea-changemaker` &middot; **Subdomain:** `git.DOMAIN`
[:octicons-arrow-right-24: Gitea Docs](https://docs.gitea.com/){ target="_blank" }
</div>
---
## Utilities
<div class="grid cards" markdown>
- :material-qrcode:{ .lg .middle } **Mini QR**
---
Lightweight QR code generator. Produces PNG images for walk sheets, campaign materials, and event signage. Embedded in the admin dashboard via iframe.
**Port:** `8089` &middot; **Container:** `mini-qr` &middot; **Subdomain:** `qr.DOMAIN`
- :material-home:{ .lg .middle } **Homepage**
---
Service dashboard showing the status of all containers at a glance. Auto-generated `services.yaml` from `config.sh` provides both production and local links.
**Port:** `3010` &middot; **Container:** `homepage-changemaker` &middot; **Subdomain:** `home.DOMAIN`
[:octicons-arrow-right-24: Homepage Docs](https://gethomepage.dev/){ target="_blank" }
- :material-draw:{ .lg .middle } **Excalidraw**
---
Collaborative whiteboard for brainstorming, diagramming, and visual planning. Real-time collaboration via WebSocket.
**Port:** `8090` &middot; **Container:** `excalidraw-changemaker` &middot; **Subdomain:** `draw.DOMAIN`
[:octicons-arrow-right-24: Excalidraw](https://excalidraw.com/){ target="_blank" }
</div>
---
## Networking & Tunneling
<div class="grid cards" markdown>
- :material-tunnel:{ .lg .middle } **Pangolin + Newt**
---
Self-hosted tunnel server with the Newt client container. Exposes your services to the internet without port forwarding. Handles SSL/TLS, works behind CGNAT and double NAT.
**Container:** `newt-changemaker` &middot; Managed from **Admin &rarr; Settings &rarr; Tunnel**
[:octicons-arrow-right-24: Deployment Guide](../deployment/index.md#pangolin) &middot; [:octicons-arrow-right-24: Pangolin GitHub](https://github.com/fosrl/pangolin){ target="_blank" }
</div>
---
## Monitoring Stack
These services run behind the `monitoring` Docker Compose profile. Start them with:
```bash
docker compose --profile monitoring up -d
```
<div class="grid cards" markdown>
- :material-chart-line:{ .lg .middle } **Prometheus**
---
Metrics collection and time-series database. Scrapes 12 custom `cm_*` application metrics plus container, host, and Redis metrics. Pre-configured alert rules.
**Port:** `9090` &middot; **Container:** `prometheus-changemaker`
[:octicons-arrow-right-24: Prometheus Docs](https://prometheus.io/docs/){ target="_blank" }
- :material-chart-box:{ .lg .middle } **Grafana**
---
Metrics visualization with 3 auto-provisioned dashboards: API Overview, Infrastructure, and Campaign Activity. Supports custom dashboards and alerting.
**Port:** `3001` &middot; **Container:** `grafana-changemaker` &middot; **Subdomain:** `grafana.DOMAIN`
[:octicons-arrow-right-24: Grafana Docs](https://grafana.com/docs/grafana/latest/){ target="_blank" }
- :material-bell-alert:{ .lg .middle } **Alertmanager**
---
Alert routing and notification delivery. Receives alerts from Prometheus and dispatches to Gotify, email, or webhooks based on configurable rules.
**Port:** `9093` &middot; **Container:** `alertmanager-changemaker`
[:octicons-arrow-right-24: Alertmanager Docs](https://prometheus.io/docs/alerting/latest/alertmanager/){ target="_blank" }
- :material-docker:{ .lg .middle } **cAdvisor**
---
Container resource metrics. Exposes CPU, memory, network, and filesystem usage per container for Prometheus to scrape.
**Port:** `8080` &middot; **Container:** `cadvisor-changemaker`
[:octicons-arrow-right-24: cAdvisor GitHub](https://github.com/google/cadvisor){ target="_blank" }
- :material-server:{ .lg .middle } **Node Exporter**
---
Host system metrics. Reports CPU, memory, disk, and network stats for the underlying server.
**Port:** `9100` &middot; **Container:** `node-exporter-changemaker`
[:octicons-arrow-right-24: Node Exporter](https://prometheus.io/docs/guides/node-exporter/){ target="_blank" }
- :material-database-export:{ .lg .middle } **Redis Exporter**
---
Redis metrics for Prometheus. Exposes connection counts, memory usage, command stats, and keyspace info.
**Port:** `9121` &middot; **Container:** `redis-exporter-changemaker`
[:octicons-arrow-right-24: Redis Exporter GitHub](https://github.com/oliver006/redis_exporter){ target="_blank" }
- :material-cellphone-message:{ .lg .middle } **Gotify**
---
Self-hosted push notification server. Receives alerts from Alertmanager and delivers them to mobile/desktop clients.
**Port:** `8889` &middot; **Container:** `gotify-changemaker`
[:octicons-arrow-right-24: Gotify Docs](https://gotify.net/docs/){ target="_blank" }
</div>
---
## Quick Reference
All services at a glance with their default ports and subdomains.
| Service | Port | Subdomain | Docker Profile |
|---------|------|-----------|---------------|
| Express API | 4000 | `api.` | default |
| Media API | 4100 | `media.` | default |
| Admin GUI | 3000 | `app.` | default |
| PostgreSQL | 5433 | &mdash; | default |
| Redis | 6379 | &mdash; | default |
| Nginx | 80/443 | *(all)* | default |
| Listmonk | 9001 | `listmonk.` | default |
| MailHog | 8025 | `mail.` | default |
| MkDocs (dev) | 4003 | `docs.` | default |
| MkDocs (static) | 4001 | *(root)* | default |
| Code Server | 8888 | `code.` | default |
| NocoDB | 8091 | `db.` | default |
| n8n | 5678 | `n8n.` | default |
| Gitea | 3030 | `git.` | default |
| Mini QR | 8089 | `qr.` | default |
| Homepage | 3010 | `home.` | default |
| Excalidraw | 8090 | `draw.` | default |
| Newt (tunnel) | &mdash; | &mdash; | default |
| Prometheus | 9090 | &mdash; | `monitoring` |
| Grafana | 3001 | `grafana.` | `monitoring` |
| Alertmanager | 9093 | &mdash; | `monitoring` |
| cAdvisor | 8080 | &mdash; | `monitoring` |
| Node Exporter | 9100 | &mdash; | `monitoring` |
| Redis Exporter | 9121 | &mdash; | `monitoring` |
| Gotify | 8889 | &mdash; | `monitoring` |
!!! tip "Starting services selectively"
You don't need to run everything. Start only what you need:
```bash
# Core only
docker compose up -d v2-postgres redis api admin
# Add nginx for subdomain routing
docker compose up -d nginx
# Add monitoring
docker compose --profile monitoring up -d
```
See [Getting Started](../getting-started/index.md) for the recommended startup order.

View File

@@ -0,0 +1,48 @@
---
title: Troubleshooting
description: Solutions for common errors, CORS issues, database problems, and tunnel debugging.
icon: material/bug
---
# Troubleshooting
Common issues and their solutions when running Changemaker Lite.
!!! warning "Under Construction"
This troubleshooting guide is being expanded. Check back soon for more solutions.
## CORS Errors in Production
**Symptom:** Browser console shows CORS errors when accessing production domain.
**Fix:** Add your production domain to `CORS_ORIGINS` in `.env`:
```bash
CORS_ORIGINS=https://app.yourdomain.org,http://localhost:3000
```
Then restart the API: `docker compose restart api`
## Pangolin Tunnel 403/302 Errors
**Symptom:** All API endpoints return 302 redirects to Pangolin auth page.
**Fix:** In the Pangolin dashboard, set each resource to "Not Protected" (public access).
## Database Connection Failures
1. Check PostgreSQL: `docker compose ps v2-postgres`
2. Verify `DATABASE_URL` in `.env`
3. View logs: `docker compose logs v2-postgres --tail 50`
## Redis Connection Failures
1. Check Redis: `docker compose ps redis-changemaker`
2. Verify `REDIS_PASSWORD` and `REDIS_URL` format in `.env`
3. Test: `docker compose exec redis-changemaker redis-cli -a $REDIS_PASSWORD ping`
## API Not Starting
1. Check logs: `docker compose logs api --tail 100`
2. Verify all required env vars are set (see `.env.example`)
3. Run migrations: `docker compose exec api npx prisma migrate deploy`

View File

@@ -0,0 +1,26 @@
---
title: Volunteer Guide
description: Guide for volunteers using the canvassing and shift signup features.
icon: material/walk
---
# Volunteer Guide
This guide helps campaign volunteers get started with shifts, canvassing, and activity tracking.
!!! warning "Under Construction"
The volunteer guide is being written. Check back soon.
## Getting Started as a Volunteer
1. **Sign up for a shift** — visit the public shifts page and register for available time slots
2. **Log in** — use the credentials from your confirmation email
3. **View assignments** — see your assigned cuts and shifts in the volunteer portal
4. **Start canvassing** — open the full-screen map, start a session, and record visits
## Volunteer Portal Features
- **Assignments** — see your upcoming shifts and assigned canvassing areas
- **Canvass Map** — GPS-tracked map with walking routes and visit recording
- **Activity Log** — review your visit history and outcome breakdowns
- **Routes** — view past canvassing routes

View File

@@ -18,22 +18,36 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
Hook that runs when MkDocs loads the configuration.
Injects environment variables as extra JavaScript.
"""
import re
# Read environment variables (with fallbacks)
media_api_url = os.environ.get('MEDIA_API_PUBLIC_URL', 'http://localhost:4100')
public_url = os.environ.get('ADMIN_URL', 'http://localhost:3000')
# For production, check for public-facing URLs
# Fallback to subdomain-based URLs if available
media_api_port = os.environ.get('MEDIA_API_PORT', '4100')
admin_port = os.environ.get('ADMIN_PORT', '3000')
admin_url = os.environ.get('ADMIN_URL', '')
base_domain = os.environ.get('BASE_DOMAIN', '')
if base_domain and not base_domain.startswith('http'):
base_domain = f'https://{base_domain}'
# Use base_domain to construct URLs if env vars not explicitly set
if media_api_url == 'http://localhost:4100' and base_domain:
media_api_url = base_domain.replace('cmlite.org', 'media.cmlite.org')
# Helper: detect Docker container hostnames (not browser-accessible)
def is_docker_hostname(url: str) -> bool:
"""Check if URL uses a Docker container hostname instead of localhost/domain."""
host = re.sub(r'^https?://', '', url).split(':')[0].split('/')[0]
# Docker hostnames typically contain hyphens and no dots (not localhost, not a domain)
return host != 'localhost' and '.' not in host
if public_url == 'http://localhost:3000' and base_domain:
public_url = base_domain.replace('cmlite.org', 'app.cmlite.org')
# Resolve media API URL — must be browser-accessible (not Docker hostname)
if is_docker_hostname(media_api_url):
media_api_url = f'http://localhost:{media_api_port}'
# Resolve public URL (admin app)
if admin_url and not is_docker_hostname(admin_url) and 'localhost' not in admin_url:
# Production domain — use as-is
public_url = admin_url
else:
# Dev: use ADMIN_PORT (most reliable source of truth)
public_url = f'http://localhost:{admin_port}'
# Create inline JavaScript with config
config_script = f"""
@@ -61,18 +75,25 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
# Note: We'll need to create a file for this
env_config_path = 'assets/js/env-config.js'
# Write the generated config to a file
# Write the generated config to a file (only if content changed to avoid
# triggering MkDocs file watcher rebuild loop in serve mode)
import pathlib
docs_dir = pathlib.Path(config['docs_dir'])
env_config_file = docs_dir / env_config_path
env_config_file.parent.mkdir(parents=True, exist_ok=True)
with open(env_config_file, 'w') as f:
f.write(config_script)
existing_content = ''
if env_config_file.exists():
existing_content = env_config_file.read_text()
logger.info(f"✓ Generated video config: {env_config_file}")
logger.info(f" MEDIA_API_URL: {media_api_url}")
logger.info(f" PUBLIC_URL: {public_url}")
if existing_content != config_script:
with open(env_config_file, 'w') as f:
f.write(config_script)
logger.info(f"✓ Generated video config: {env_config_file}")
logger.info(f" MEDIA_API_URL: {media_api_url}")
logger.info(f" PUBLIC_URL: {public_url}")
else:
logger.info(f"✓ Video config unchanged, skipping write")
# Insert at the beginning of extra_javascript list
if env_config_path not in config['extra_javascript']:

View File

@@ -99,7 +99,7 @@ def generate_repo_data(repo_config: Dict[str, Any], output_dir: Path) -> None:
if repo_config.get('github'):
api_url = f"https://api.github.com/repos/{repo}"
headers = {'Accept': 'application/vnd.github.v3+json'}
github_token = "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" # Replace with your GitHub token
github_token = os.getenv('GITHUB_TOKEN', '')
if github_token:
headers['Authorization'] = f'token {github_token}'
else:

View File

@@ -1,4 +0,0 @@
# Canvas
This is BNKops canvassing how to! In the following document, you will find all sorts of tips and tricks for door knocking, canvassing, and using the BNKops canvassing app.

View File

@@ -4,160 +4,3 @@ hide:
- navigation
- toc
---
# Welcome to Changemaker Lite
Stop feeding your secrets to corporations. Own your political infrastructure.
!!! success "Changemaker Lite V2 is Now Available"
**V2 is a complete architectural rebuild** with a modern TypeScript stack, dual API design, React admin interface, and comprehensive feature modules. Production ready with security audit completed.
[→ Explore V2 Documentation](v2/index.md){ .md-button .md-button--primary }
[→ Quick Start Guide](v2/getting-started/quick-start.md){ .md-button }
## Changemaker Lite V2
V2 is the **recommended version** for all new installations. It offers:
### ✨ Modern Architecture
- **Dual API Design**: Express.js (main features) + Fastify (media library)
- **TypeScript Throughout**: Type-safe development with better IDE support
- **Prisma + Drizzle ORM**: Direct database access (no NocoDB middleware)
- **React Admin**: Modern UI with Vite + Ant Design + Zustand
### 🚀 Comprehensive Features
- **Influence Module**: Email advocacy campaigns targeting elected representatives
- **Map Module**: Geographic mapping with GPS-tracked canvassing
- **Landing Pages**: GrapesJS page builder with MkDocs export
- **Email Templates**: Template management system
- **Media Library**: Video management with public gallery
- **Newsletter Integration**: Listmonk sync for email marketing
- **Observability**: Prometheus + Grafana monitoring stack
### 🔒 Production Ready
- **Security Audited**: 13 findings addressed (Feb 2026)
- **JWT Authentication**: Role-based access control with refresh tokens
- **Password Policy**: Enforced complexity requirements
- **Rate Limiting**: Per-endpoint protection
- **Monitoring**: 12 custom metrics + 3 Grafana dashboards
### 📚 Complete Documentation
- [Getting Started](v2/getting-started/index.md) - Installation and setup
- [Architecture](v2/architecture/index.md) - System design and components
- [Features](v2/features/index.md) - Module documentation
- [API Reference](v2/api-reference/index.md) - Complete endpoint docs
- [User Guides](v2/user-guides/index.md) - Role-based manuals
[**Explore V2 Documentation →**](v2/index.md)
---
## Changemaker Lite V1 (Legacy)
!!! warning "V1 is Deprecated"
V1 documentation is preserved below for reference. **We strongly recommend migrating to V2** for improved performance, security, and features.
[→ View Migration Guide](v2/migration/index.md)
### Quick Start (V1)
Get V1 running in minutes:
```bash
# Clone the repository
git clone https://gitea.bnkops.com/admin/changemaker.lite
cd changemaker.lite
# Configure environment
./config.sh
# Start all services
docker compose up -d
# For production deployment with Cloudflare tunnels
./start-production.sh
```
## Services
Changemaker Lite includes these essential services:
### Core Services
- **[Homepage](v1/services/homepage.md)** (Port 3010) - Central dashboard and service monitoring
- **[Code Server](v1/services/code-server.md)** (Port 8888) - VS Code in your browser
- **[MkDocs](v1/services/mkdocs.md)** (Port 4000) - Documentation with live preview
- **[Static Server](v1/services/static-server.md)** (Port 4001) - Production documentation site
### Communication & Automation
- **[Listmonk](v1/services/listmonk.md)** (Port 9000) - Newsletter and email campaign management
- **[n8n](v1/services/n8n.md)** (Port 5678) - Workflow automation platform
### Data & Development
- **[NocoDB](v1/services/nocodb.md)** (Port 8090) - No-code database platform
- **[PostgreSQL](v1/services/postgresql.md)** (Port 5432) - Database backend for Listmonk
- **[Gitea](v1/services/gitea.md)** (Port 3030) - Self-hosted Git service
### Interactive Tools
- **[Map Viewer](v1/services/map.md)** (Port 3000) - Interactive map with NocoDB integration
- **[Mini QR](v1/services/mini-qr.md)** (Port 8089) - QR code generator
## Getting Started (V1)
1. **Setup**: Run `./config.sh` to configure your environment
2. **Launch**: Start services with `docker compose up -d`
3. **Dashboard**: Access the Homepage at [http://localhost:3010](http://localhost:3010)
4. **Production**: Deploy with Cloudflare tunnels using `./start-production.sh`
## Project Structure
```
changemaker.lite/
├── docker-compose.yml # Service definitions
├── config.sh # Configuration wizard
├── start-production.sh # Production deployment script
├── mkdocs/ # Documentation source
│ ├── docs/ # Markdown files
│ └── mkdocs.yml # MkDocs configuration
├── configs/ # Service configurations
│ ├── homepage/ # Homepage dashboard config
│ ├── code-server/ # VS Code settings
│ └── cloudflare/ # Tunnel configurations
├── map/ # Map application
│ ├── app/ # Node.js application
│ ├── Dockerfile # Container definition
│ └── .env # Map configuration
└── assets/ # Shared assets
├── images/ # Image files
├── icons/ # Service icons
└── uploads/ # Listmonk uploads
```
## Key Features
- 🐳 **Fully Containerized** - All services run in Docker containers
- 🔒 **Production Ready** - Built-in Cloudflare tunnel support for secure access
- 📦 **All-in-One** - Everything you need for documentation, development, and campaigns
- 🗺️ **Geographic Data** - Interactive maps with real-time location tracking
- 📧 **Email Campaigns** - Professional newsletter management
- 🔄 **Automation** - Connect services and automate workflows
- 💾 **Version Control** - Self-hosted Git repository
- 🎯 **No-Code Database** - Build applications without programming
## System Requirements
- **OS**: Ubuntu 24.04 LTS (Noble Numbat) or compatible Linux distribution
- **Docker**: Version 24.0+ with Docker Compose v2
- **Memory**: Minimum 4GB RAM (8GB recommended)
- **Storage**: 20GB+ available disk space
- **Network**: Internet connection for initial setup
## Learn More (V1)
- [Getting Started](v1/build/index.md) - Detailed installation guide
- [Services Overview](v1/services/index.md) - Deep dive into each service
- [Blog](blog/index.md) - Updates and tutorials
- [GitHub Repository](https://gitea.bnkops.com/admin/Changemaker) - Source code

File diff suppressed because it is too large Load Diff

View File

@@ -1,203 +0,0 @@
# Cost Comparison: Corporation vs. Community
## The True Cost of Corporate Dependency
When movements choose corporate software, they're not just paying subscription fees—they're paying with their power, their privacy, and their future. Let's break down the real costs.
## Monthly Cost Analysis
### Small Campaign (50 supporters, 5,000 emails/month)
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|------------------|-------------------|--------------|------------------|--------------|
| **Email Marketing** | Mailchimp | $59/month | Listmonk | $0* |
| **Database & CRM** | Airtable Pro | $240/month | NocoDB | $0* |
| **Website Hosting** | Squarespace | $40/month | Static Server | $0* |
| **Documentation** | Notion Team | $96/month | MkDocs | $0* |
| **Development** | GitHub Codespaces | $87/month | Code Server | $0* |
| **Automation** | Zapier Professional | $73/month | n8n | $0* |
| **File Storage** | Google Workspace | $72/month | PostgreSQL + Storage | $0* |
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
| **TOTAL** | | **$667/month** | | **$50/month** |
*\*Included in base Changemaker Lite hosting cost*
*†Privacy costs are incalculable but include surveillance, data sales, and community manipulation*
---
### Medium Campaign (500 supporters, 50,000 emails/month)
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|------------------|-------------------|--------------|------------------|--------------|
| **Email Marketing** | Mailchimp | $299/month | Listmonk | $0* |
| **Database & CRM** | Airtable Pro | $600/month | NocoDB | $0* |
| **Website Hosting** | Squarespace | $65/month | Static Server | $0* |
| **Documentation** | Notion Team | $240/month | MkDocs | $0* |
| **Development** | GitHub Codespaces | $174/month | Code Server | $0* |
| **Automation** | Zapier Professional | $146/month | n8n | $0* |
| **File Storage** | Google Workspace | $144/month | PostgreSQL + Storage | $0* |
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
| **TOTAL** | | **$1,668/month** | | **$75/month** |
---
### Large Campaign (5,000 supporters, 500,000 emails/month)
| Service Category | Corporate Solution | Monthly Cost | Changemaker Lite | Monthly Cost |
|------------------|-------------------|--------------|------------------|--------------|
| **Email Marketing** | Mailchimp | $1,499/month | Listmonk | $0* |
| **Database & CRM** | Airtable Pro | $1,200/month | NocoDB | $0* |
| **Website Hosting** | Squarespace + CDN | $120/month | Static Server | $0* |
| **Documentation** | Notion Team | $480/month | MkDocs | $0* |
| **Development** | GitHub Codespaces | $348/month | Code Server | $0* |
| **Automation** | Zapier Professional | $292/month | n8n | $0* |
| **File Storage** | Google Workspace | $288/month | PostgreSQL + Storage | $0* |
| **Analytics** | Corporate tracking | Privacy cost† | Self-hosted | $0* |
| **TOTAL** | | **$4,227/month** | | **$150/month** |
## Annual Savings Breakdown
### 3-Year Cost Comparison
| Campaign Size | Corporate Total | Changemaker Total | **Savings** |
|---------------|----------------|-------------------|-------------|
| **Small** | $24,012 | $1,800 | **$22,212** |
| **Medium** | $60,048 | $2,700 | **$57,348** |
| **Large** | $152,172 | $5,400 | **$146,772** |
## Hidden Costs of Corporate Software
### What You Can't Put a Price On
#### Privacy Violations
- **Data Harvesting**: Every interaction monitored and stored
- **Behavioral Profiling**: Your community mapped and analyzed
- **Third-Party Sales**: Your data sold to unknown entities
- **Government Access**: Warrantless surveillance through corporate partnerships
#### Political Manipulation
- **Algorithmic Suppression**: Your content reach artificially limited
- **Narrative Control**: Corporate interests shape what your community sees
- **Shadow Banning**: Activists systematically de-platformed
- **Counter-Intelligence**: Your strategies monitored by opposition
#### Movement Disruption
- **Dependency Creation**: Critical infrastructure controlled by adversaries
- **Community Fragmentation**: Platforms designed to extract attention, not build power
- **Organizing Interference**: Corporate algorithms prioritize engagement over solidarity
- **Cultural Assimilation**: Movement culture shaped by corporate values
## The Changemaker Advantage
### What You Get for $50-150/month
#### Complete Infrastructure
- **Email System**: Unlimited contacts, unlimited sends
- **Database Power**: Unlimited records, unlimited complexity
- **Web Presence**: Unlimited sites, unlimited traffic
- **Development Environment**: Full coding environment with AI assistance
- **Documentation Platform**: Beautiful, searchable knowledge base
- **Automation Engine**: Connect everything, automate everything
- **File Storage**: Unlimited files, unlimited backups
#### True Ownership
- **Your Domain**: No corporate branding or limitations
- **Your Data**: Complete export capability, no lock-in
- **Your Rules**: No terms of service to violate
- **Your Community**: No algorithmic manipulation
#### Community Support
- **Open Documentation**: Complete guides and tutorials available
- **Community-Driven Development**: Built by and for liberation movements
- **Technical Support**: Professional assistance from BNKops cooperative
- **Political Alignment**: Technology designed with movement values
## The Compound Effect
### Year Over Year Savings
Corporate software costs grow exponentially:
- **Year 1**: "Starter" pricing to hook you
- **Year 2**: Feature limits force tier upgrades
- **Year 3**: Usage growth triggers premium pricing
- **Year 4**: Platform changes force expensive migrations
- **Year 5**: Lock-in enables arbitrary price increases
Changemaker Lite costs grow linearly with actual infrastructure needs:
- **Year 1**: Base infrastructure costs
- **Year 2**: Modest increases for storage/bandwidth only
- **Year 3**: Scale only with actual technical requirements
- **Year 4**: Community-driven improvements at no extra cost
- **Year 5**: Established infrastructure with declining per-user costs
### 10-Year Projection
| Year | Corporate (Medium Campaign) | Changemaker Lite | Annual Savings |
|------|---------------------------|------------------|----------------|
| 1 | $20,016 | $900 | $19,116 |
| 2 | $22,017 | $900 | $21,117 |
| 3 | $24,219 | $1,080 | $23,139 |
| 4 | $26,641 | $1,080 | $25,561 |
| 5 | $29,305 | $1,260 | $28,045 |
| 6 | $32,235 | $1,260 | $30,975 |
| 7 | $35,459 | $1,440 | $34,019 |
| 8 | $39,005 | $1,440 | $37,565 |
| 9 | $42,905 | $1,620 | $41,285 |
| 10 | $47,196 | $1,620 | $45,576 |
| **TOTAL** | **$318,998** | **$12,600** | **$306,398** |
## Calculate Your Own Savings
### Current Corporate Costs Worksheet
**Email Marketing**: $____/month
**Database/CRM**: $____/month
**Website Hosting**: $____/month
**Documentation**: $____/month
**Development Tools**: $____/month
**Automation**: $____/month
**File Storage**: $____/month
**Other SaaS**: $____/month
**Monthly Total**: $____
**Annual Total**: $____
**Changemaker Alternative**: $50-150/month
**Your Annual Savings**: $____
## Beyond the Numbers
### What Movements Do With Their Savings
The money saved by choosing community-controlled technology doesn't disappear—it goes directly back into movement building:
- **Hire organizers** instead of paying corporate executives
- **Fund direct actions** instead of funding surveillance infrastructure
- **Support community members** instead of enriching shareholders
- **Build lasting power** instead of temporary platform dependency
## Making the Switch
### Transition Strategy
You don't have to switch everything at once:
1. **Start with documentation** - Move your knowledge base to MkDocs
2. **Add email infrastructure** - Set up Listmonk for newsletters
3. **Build your database** - Move contact management to NocoDB
4. **Automate connections** - Use n8n to integrate everything
5. **Phase out corporate tools** - Cancel subscriptions as you replicate functionality
### Investment Timeline
- **Month 1**: Initial setup and learning ($150 including setup time)
- **Month 2-3**: Data migration and team training ($100/month)
- **Month 4+**: Full operation at optimal cost ($50-150/month based on scale)
### ROI Calculation
Most campaigns recover their entire first-year investment in **60-90 days** through subscription savings alone.
---
*Ready to stop feeding your budget to corporate surveillance? [Get started with Changemaker Lite today](../build/index.md) and take control of your digital infrastructure.*

View File

@@ -1,163 +0,0 @@
# Philosophy: Your Secrets, Your Power, Your Movement
## The Question That Changes Everything!
**If you are a political actor, who do you trust with your secrets?**
This isn't just a technical question—it's the core political question of our time. Every email you send, every document you create, every contact list you build, every strategy you develop: where does it live? Who owns the servers? Who has the keys?
## The Corporate Extraction Machine
### How They Hook You
Corporate software companies have perfected the art of digital snake oil sales:
1. **Free Trials** - They lure you in with "free" accounts
2. **Feature Creep** - Essential features require paid tiers
3. **Data Lock-In** - Your data becomes harder to export
4. **Price Escalation** - $40/month becomes $750/month as you grow
5. **Surveillance Integration** - Your organizing becomes their intelligence
### The Real Product
!!! warning "You Are Not the Customer"
If you're not paying for the product, you ARE the product. But even when you are paying, you're often still the product.
Corporate platforms don't make money from your subscription fees—they make money from:
- **Data Sales** to third parties
- **Algorithmic Manipulation** for corporate and political interests
- **Surveillance Contracts** with governments and corporations
- **Predictive Analytics** about your community and movement
## The BNKops Alternative
### Who We Are
**BNKops** is a cooperative based in amiskwaciy-wâskahikan (Edmonton, Alberta) on Treaty 6 territory. We're not a corporation—we're a collective of skilled organizers, developers, and community builders who believe technology should serve liberation, not oppression.
### Our Principles
#### 🏳️‍⚧️ 🏳️‍🌈 🇵🇸 Liberation First
Technology that centers the most marginalized voices and fights for collective liberation. We believe strongly that the medium is the message; if you the use the medium of fascists, what does that say about your movement?
#### 🤝 Community Over Profit
We operate as a cooperative because we believe in shared ownership and democratic decision-making. No venture capitalists, no shareholders, no extraction.
#### ⚡ Data Sovereignty
Your data belongs to you and your community. We build tools that let you own your digital infrastructure completely.
#### 🔒 Security Culture
Real security comes from community control, not corporate promises. We integrate security culture practices into our technology design.
### Why This Matters
When you control your technology infrastructure:
- **Your secrets stay secret** - No corporate access to sensitive organizing data
- **Your community stays connected** - No algorithmic manipulation of your reach
- **Your costs stay low** - No extraction-based pricing as you grow
- **Your future stays yours** - No vendor lock-in or platform dependency
## The Philosophy in Practice
### Security Culture Meets Technology
Traditional security culture asks: "Who needs to know this information?"
Digital security culture asks: "Who controls the infrastructure where this information lives?"
### Community Technology
We believe in **community technology** - tools that:
- Are owned and controlled by the communities that use them
- Are designed with liberation politics from the ground up using free and open source software
- Prioritize care, consent, and collective power
- Can be understood, modified, and improved by community members
### Prefigurative Politics
The tools we use shape the movements we build. Corporate tools create corporate movements—hierarchical, surveilled, and dependent. Community-controlled tools create community-controlled movements—democratic, secure, and sovereign.
## Common Questions
### "Isn't this just for tech people?"
**No.** We specifically designed Changemaker Lite for organizers, activists, and movement builders who may not have technical backgrounds. Our philosophy is that everyone deserves digital sovereignty, not just people with computer science degrees.
This is not to say that you won't need to learn! These tools are just that; tools. They have no fancy or white-labeled marketing and are technical in nature. You will need to learn to use them, just as any worker needs to learn the power tools they use on the job.
### "What about convenience?"
Corporate platforms are convenient because they've extracted billions of dollars from users to fund that convenience. When you own your tools, there's a learning curve—but it's the same learning curve as learning to organize, learning to build power, learning to create change.
### "Can't we just use corporate tools carefully?"
Would you hold your most sensitive organizing meetings in a room owned by your opposition? Would you store your membership lists in filing cabinets at a corporation that profits from surveillance? Digital tools are the same.
### "What about security?"
Real security comes from community control, not corporate promises. When you control your infrastructure:
- You decide what gets logged and what doesn't
- You choose who has access and who doesn't
- You know exactly where your data is and who can see it
- You can't be de-platformed or locked out of your own data
### The Surveillance Capitalism Trap
As Shoshana Zuboff documents in "The Age of Surveillance Capitalism," we're living through a new form of capitalism that extracts value from human experience itself. Political movements are particularly valuable targets because:
- Political data predicts behavior
- Movement intelligence can be used to counter-organize
- Community networks can be mapped and disrupted
- Organizing strategies can be monitored and neutralized
## Taking Action
### Start Where You Are
You don't have to replace everything at once. Start with one tool, one campaign, one project. Learn the technology alongside your organizing.
### Build Community Capacity
The goal isn't individual self-sufficiency—it's community technological sovereignty. Share skills, pool resources, learn together.
### Connect with Others
You're not alone in this. The free and open source software community, the digital security community, and the appropriate technology movement are all working on similar problems.
### Remember Why
This isn't about technology for its own sake. It's about building the infrastructure for the world we want to see—where communities have power, where people control their own data, where technology serves liberation.
---
## Resources for Deeper Learning
### Essential Reading
- [De-corp Your Software Stack](https://docs.bnkops.com/archive/repo.archive/thatreallyblondehuman/Thoughts%20🤔/If%20you%20do%20politics%20who%20is%20reading%20your%20secrets%20-%20why%20you%20should%20de-corp%20your%20software%20stack/) - Our full manifesto
- [The Age of Surveillance Capitalism](https://en.wikipedia.org/wiki/The_Age_of_Surveillance_Capitalism) by Shoshana Zuboff
- [Security Culture Handbook](https://docs.bnkops.com/archive/repo.archive/Zines%20We%20Like%20😎/What%20Is%20Security%20Culture%20☠/)
### Community Resources
- [BNKops Repository](https://docs.bnkops.com/) - Documentation and knowledge base
- [Activist Handbook](https://activist.org/) - Movement building resources
- [EFF Surveillance Self-Defense](https://ssd.eff.org/) - Digital security guides
### Technical Learning
- [Self-Hosted Awesome List](https://github.com/awesome-selfhosted/awesome-selfhosted) - Open source alternatives
- [Linux Journey](https://linuxjourney.com/) - Learn Linux basics
- [Docker Curriculum](https://docker-curriculum.com/) - Learn containerization
---
*This philosophy document is a living document. Contribute your thoughts, experiences, and improvements through the [BNKops documentation platform](https://docs.bnkops.com/).*

25
mkdocs/docs/test.md Normal file
View File

@@ -0,0 +1,25 @@
# Test
Testing page.
<div class="video-card-block" data-video-id="2" data-video-title="Testing This Sucker" data-video-duration="594" data-video-quality="" data-video-views="0" style="max-width: 480px; margin: 0 auto;">
<a href="http://app.org/gallery/watch/2" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
<div style="position: relative; padding-bottom: 56.25%; background: #0d1b2a; overflow: hidden;">
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22270%22%20viewBox%3D%220%200%20480%20270%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22270%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22135%22%20r%3D%2232%22%20fill%3D%22rgba(157%2C78%2C221%2C0.6)%22%2F%3E%3Cpolygon%20points%3D%22230%2C118%20258%2C135%20230%2C152%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E" alt="Testing This Sucker" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">9:54</span>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
<svg width="24" height="24" viewBox="0 0 20 20" fill="#fff"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"/></svg>
</div>
</div>
<div style="padding: 12px 16px;">
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Testing This Sucker</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
<span style="color: #8899aa; font-size: 13px;">0 views</span>
<span style="color: #9d4edd; font-size: 13px; font-weight: 500;">Watch &rarr;</span>
</div>
</div>
</a>
</div>

View File

@@ -1,525 +0,0 @@
# Setting Up Ansible with Tailscale for Remote Server Management
## Overview
This guide walks you through setting up Ansible to manage remote servers (like ThinkCentre units) using Tailscale for secure networking. This approach provides reliable remote access without complex port forwarding or VPN configurations.
In plainer language; this allows you to manage several Changemaker nodes remotely. If you are a full time campaigner, this can enable you to manage several campaigns infrastructure from a central location while each user gets their own Changemaker box.
## What You'll Learn
- How to set up Ansible for infrastructure automation
- How to configure secure remote access using Tailscale
- How to troubleshoot common SSH and networking issues
- Why this approach is better than alternatives like Cloudflare Tunnels for simple SSH access
## Prerequisites
- **Master Node**: Your main computer running Ubuntu/Linux (control machine)
- **Target Nodes**: Remote servers/ThinkCentres running Ubuntu/Linux
- **Both machines**: Must have internet access
- **User Account**: Same username on all machines (recommended)
## Part 1: Initial Setup on Master Node
### 1. Create Ansible Directory Structure
```bash
# Create project directory
mkdir ~/ansible_quickstart
cd ~/ansible_quickstart
# Create directory structure
mkdir -p group_vars host_vars roles playbooks
```
### 2. Install Ansible
```bash
sudo apt update
sudo apt install ansible
```
### 3. Generate SSH Keys (if not already done)
```bash
# Generate SSH key pair
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
# Display public key (save this for later)
cat ~/.ssh/id_rsa.pub
```
## Part 2: Target Node Setup (Physical Access Required Initially)
### 1. Enable SSH on Target Node
Access each target node physically (monitor + keyboard):
```bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install and enable SSH
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh
# Check SSH status
sudo systemctl status ssh
```
**Note**: If you get "Unit ssh.service could not be found", you need to install the SSH server first:
```bash
# Install OpenSSH server
sudo apt install openssh-server
# Then start and enable SSH
sudo systemctl start ssh
sudo systemctl enable ssh
# Verify SSH is running and listening
sudo ss -tlnp | grep :22
```
You should see SSH listening on port 22.
### 2. Configure SSH Key Authentication
```bash
# Create .ssh directory
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Create authorized_keys file
nano ~/.ssh/authorized_keys
```
Paste your public key from the master node, then:
```bash
# Set proper permissions
chmod 600 ~/.ssh/authorized_keys
```
### 3. Configure SSH Security
```bash
# Edit SSH config
sudo nano /etc/ssh/sshd_config
```
Ensure these lines are uncommented:
```
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
```
```bash
# Restart SSH service
sudo systemctl restart ssh
```
### 4. Configure Firewall
```bash
# Check firewall status
sudo ufw status
# Allow SSH through firewall
sudo ufw allow ssh
# Fix home directory permissions (required for SSH keys)
chmod 755 ~/
```
## Part 3: Test Local SSH Connection
Before proceeding with remote access, test SSH connectivity locally:
```bash
# From master node, test SSH to target
ssh username@<target-local-ip>
```
**Common Issues and Solutions:**
- **Connection hangs**: Check firewall rules (`sudo ufw allow ssh`)
- **Permission denied**: Verify SSH keys and file permissions
- **SSH config errors**: Ensure `PubkeyAuthentication yes` is set
## Part 4: Set Up Tailscale for Remote Access
### Why Tailscale Over Alternatives
We initially tried Cloudflare Tunnels but encountered complexity with:
- DNS routing issues
- Complex configuration for SSH
- Same-network testing problems
- Multiple configuration approaches with varying success
**Tailscale is superior because:**
- Zero configuration mesh networking
- Works from any network
- Persistent IP addresses
- No port forwarding needed
- Free for personal use
### 1. Install Tailscale on Master Node
```bash
# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Connect to Tailscale network
sudo tailscale up
```
Follow the authentication URL to connect with your Google/Microsoft/GitHub account.
### 2. Install Tailscale on Target Nodes
**On each target node:**
```bash
# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Connect to Tailscale network
sudo tailscale up
```
Authenticate each device through the provided URL.
### 3. Get Tailscale IP Addresses
**On each machine:**
```bash
# Get your Tailscale IP
tailscale ip -4
```
Each device receives a persistent IP like `100.x.x.x`.
## Part 5: Configure Ansible
### 1. Create Inventory File
```bash
# Create inventory.ini
cd ~/ansible_quickstart
nano inventory.ini
```
**Content:**
```ini
[thinkcenter]
tc-node1 ansible_host=100.x.x.x ansible_user=your-username
tc-node2 ansible_host=100.x.x.x ansible_user=your-username
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_host_key_checking=False
```
Replace:
- `100.x.x.x` with actual Tailscale IPs
- `your-username` with your actual username
### 2. Test Ansible Connectivity
```bash
# Test connection to all nodes
ansible all -i inventory.ini -m ping
```
**Expected output:**
```
tc-node1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
```
## Part 6: Create and Run Playbooks
### 1. Simple Information Gathering Playbook
```bash
mkdir -p playbooks
nano playbooks/info-playbook.yml
```
**Content:**
```yaml
---
- name: Gather Node Information
hosts: all
tasks:
- name: Get system information
setup:
- name: Display basic system info
debug:
msg: |
Hostname: {{ ansible_hostname }}
Operating System: {{ ansible_distribution }} {{ ansible_distribution_version }}
Architecture: {{ ansible_architecture }}
Memory: {{ ansible_memtotal_mb }}MB
CPU Cores: {{ ansible_processor_vcpus }}
- name: Show disk usage
command: df -h /
register: disk_info
- name: Display disk usage
debug:
msg: "Root filesystem usage: {{ disk_info.stdout_lines[1] }}"
- name: Check uptime
command: uptime
register: uptime_info
- name: Display uptime
debug:
msg: "System uptime: {{ uptime_info.stdout }}"
```
### 2. Run the Playbook
```bash
ansible-playbook -i inventory.ini playbooks/info-playbook.yml
```
## Part 7: Advanced Playbook Example
### System Setup Playbook
```bash
nano playbooks/setup-node.yml
```
**Content:**
```yaml
---
- name: Setup ThinkCentre Node
hosts: all
become: yes
tasks:
- name: Update package cache
apt:
update_cache: yes
- name: Install essential packages
package:
name:
- htop
- vim
- curl
- git
- docker.io
state: present
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
- name: Create management directory
file:
path: /opt/management
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
```
## Troubleshooting Guide
### SSH Issues
**Problem: SSH connection hangs**
- Check firewall: `sudo ufw status` and `sudo ufw allow ssh`
- Verify SSH service: `sudo systemctl status ssh`
- Test local connectivity first
**Problem: Permission denied (publickey)**
- Check SSH key permissions: `chmod 600 ~/.ssh/authorized_keys`
- Verify home directory permissions: `chmod 755 ~/`
- Ensure SSH config allows key auth: `PubkeyAuthentication yes`
**Problem: Bad owner or permissions on SSH config**
```bash
chmod 600 ~/.ssh/config
```
### Ansible Issues
**Problem: Host key verification failed**
- Add to inventory: `ansible_host_key_checking=False`
**Problem: Ansible command not found**
```bash
sudo apt install ansible
```
**Problem: Connection timeouts**
- Verify Tailscale connectivity: `ping <tailscale-ip>`
- Check if both nodes are connected: `tailscale status`
### Tailscale Issues
**Problem: Can't connect to Tailscale IP**
- Verify both devices are authenticated: `tailscale status`
- Check Tailscale is running: `sudo systemctl status tailscaled`
- Restart Tailscale: `sudo tailscale up`
## Scaling to Multiple Nodes
### Adding New Nodes
1. **Install Tailscale on new node**
2. **Set up SSH access** (repeat Part 2)
3. **Add to inventory.ini:**
```ini
[thinkcenter]
tc-node1 ansible_host=100.125.148.60 ansible_user=bunker-admin
tc-node2 ansible_host=100.x.x.x ansible_user=bunker-admin
tc-node3 ansible_host=100.x.x.x ansible_user=bunker-admin
```
### Group Management
```ini
[webservers]
tc-node1 ansible_host=100.x.x.x ansible_user=bunker-admin
tc-node2 ansible_host=100.x.x.x ansible_user=bunker-admin
[databases]
tc-node3 ansible_host=100.x.x.x ansible_user=bunker-admin
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_host_key_checking=False
```
Run playbooks on specific groups:
```bash
ansible-playbook -i inventory.ini -l webservers playbook.yml
```
## Best Practices
### Security
- Use SSH keys, not passwords
- Keep Tailscale client updated
- Regular security updates via Ansible
- Use `become: yes` only when necessary
### Organization
```
ansible_quickstart/
├── inventory.ini
├── group_vars/
├── host_vars/
├── roles/
└── playbooks/
├── info-playbook.yml
├── setup-node.yml
└── maintenance.yml
```
### Monitoring and Maintenance
Create regular maintenance playbooks:
```yaml
- name: System maintenance
hosts: all
become: yes
tasks:
- name: Update all packages
apt:
upgrade: dist
update_cache: yes
- name: Clean package cache
apt:
autoclean: yes
autoremove: yes
```
## Alternative Approaches We Considered
### Cloudflare Tunnels
- **Pros**: Good for web services, handles NAT traversal
- **Cons**: Complex SSH setup, DNS routing issues, same-network problems
- **Use case**: Better for web applications than SSH access
### Traditional VPN
- **Pros**: Full network access
- **Cons**: Complex setup, port forwarding required, router configuration
- **Use case**: When you control the network infrastructure
### SSH Reverse Tunnels
- **Pros**: Simple concept
- **Cons**: Requires VPS, single point of failure, manual setup
- **Use case**: Temporary access or when other methods fail
## Conclusion
This setup provides:
- **Reliable remote access** from anywhere
- **Secure mesh networking** with Tailscale
- **Infrastructure automation** with Ansible
- **Easy scaling** to multiple nodes
- **No complex networking** required
The combination of Ansible + Tailscale is ideal for managing distributed infrastructure without the complexity of traditional VPN setups or the limitations of cloud-specific solutions.
## Quick Reference Commands
```bash
# Check Tailscale status
tailscale status
# Test Ansible connectivity
ansible all -i inventory.ini -m ping
# Run playbook on all hosts
ansible-playbook -i inventory.ini playbook.yml
# Run playbook on specific group
ansible-playbook -i inventory.ini -l groupname playbook.yml
# Run single command on all hosts
ansible all -i inventory.ini -m command -a "uptime"
# SSH to node via Tailscale
ssh username@100.x.x.x
```

View File

@@ -1,3 +0,0 @@
# Advanced Configurations
We are also publishing how BNKops does several advanced workflows. These include things like assembling hardware, how to manage a network, how to manage several changemakers simultaneously, and integrating AI.

View File

@@ -1,685 +0,0 @@
# Remote Development with VSCode over Tailscale
## Overview
This guide describes how to set up Visual Studio Code for remote development on servers using the Tailscale network. This enables development directly on remote machines as if they were local, with full access to files, terminals, and debugging capabilities.
## What You'll Learn
- How to configure VSCode for remote SSH connections
- How to set up remote development environments
- How to manage multiple remote servers efficiently
- How to troubleshoot common remote development issues
- Best practices for remote development workflows
## Prerequisites
- **Ansible + Tailscale setup completed** (see previous guide)
- **VSCode installed** on the local machine (master node)
- **Working SSH access** to remote servers via Tailscale
- **Tailscale running** on both local and remote machines
## Verify Prerequisites
Before starting, verify the setup:
```bash
# Check Tailscale connectivity
tailscale status
# Test SSH access
ssh <username>@<tailscale-ip>
# Check VSCode is installed
code --version
```
## Part 1: Install and Configure Remote-SSH Extension
### 1. Install the Remote Development Extensions
**Option A: Install Remote Development Pack (Recommended)**
1. Open VSCode
2. Press **Ctrl+Shift+X** (or **Cmd+Shift+X** on Mac)
3. Search for **"Remote Development"**
4. Install the **Remote Development extension pack** by Microsoft
This pack includes:
- Remote - SSH
- Remote - SSH: Editing Configuration Files
- Remote - Containers
- Remote - WSL (Windows only)
**Option B: Install Individual Extension**
1. Search for **"Remote - SSH"**
2. Install **Remote - SSH** by Microsoft
### 2. Verify Installation
After installation, the following should be visible:
- Remote Explorer icon in the Activity Bar (left sidebar)
- "Remote-SSH" commands in Command Palette (Ctrl+Shift+P)
## Part 2: Configure SSH Connections
### 1. Access SSH Configuration
**Method A: Through VSCode**
1. Press **Ctrl+Shift+P** to open Command Palette
2. Type **"Remote-SSH: Open SSH Configuration File..."**
3. Select the SSH config file (usually the first option)
**Method B: Direct File Editing**
```bash
# Edit SSH config file directly
nano ~/.ssh/config
```
### 2. Add Server Configurations
Add servers to the SSH config file:
```
# Example Node
Host node1
HostName <tailscale-ip>
User <username>
IdentityFile ~/.ssh/id_rsa
ForwardAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
# Additional nodes (add as needed)
Host node2
HostName <tailscale-ip>
User <username>
IdentityFile ~/.ssh/id_rsa
ForwardAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
```
**Configuration Options Explained:**
- `Host`: Friendly name for the connection
- `HostName`: Tailscale IP address
- `User`: Username on the remote server
- `IdentityFile`: Path to the SSH private key
- `ForwardAgent`: Enables SSH agent forwarding for Git operations
- `ServerAliveInterval`: Keeps connection alive (prevents timeouts)
- `ServerAliveCountMax`: Number of keepalive attempts
### 3. Set Proper SSH Key Permissions
```bash
# Ensure SSH config has correct permissions
chmod 600 ~/.ssh/config
# Verify SSH key permissions
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
```
## Part 3: Connect to Remote Servers
### 1. Connect via Command Palette
1. Press **Ctrl+Shift+P**
2. Type **"Remote-SSH: Connect to Host..."**
3. Select the server (e.g., `node1`)
4. VSCode will open a new window connected to the remote server
### 2. Connect via Remote Explorer
1. Click the **Remote Explorer** icon in Activity Bar
2. Expand **SSH Targets**
3. Click the **connect** icon next to the server name
### 3. Connect via Quick Menu
1. Click the **remote indicator** in bottom-left corner (looks like ><)
2. Select **"Connect to Host..."**
3. Choose the server from the list
### 4. First Connection Process
On first connection, VSCode will:
1. **Verify the host key** (click "Continue" if prompted)
2. **Install VSCode Server** on the remote machine (automatic)
3. **Open a remote window** with access to the remote file system
**Expected Timeline:**
- First connection: 1-3 minutes (installs VSCode Server)
- Subsequent connections: 10-30 seconds
## Part 4: Remote Development Environment Setup
### 1. Open Remote Workspace
Once connected:
```bash
# In the VSCode terminal (now running on remote server)
# Navigate to the project directory
cd /home/<username>/projects
# Open current directory in VSCode
code .
# Or open a specific project
code /opt/myproject
```
### 2. Install Extensions on Remote Server
Extensions must be installed separately on the remote server:
**Essential Development Extensions:**
1. **Python** (Microsoft) - Python development
2. **GitLens** (GitKraken) - Enhanced Git capabilities
3. **Docker** (Microsoft) - Container development
4. **Prettier** - Code formatting
5. **ESLint** - JavaScript linting
6. **Auto Rename Tag** - HTML/XML tag editing
**To Install:**
1. Go to Extensions (Ctrl+Shift+X)
2. Find the desired extension
3. Click **"Install in SSH: node1"** (not local install)
### 3. Configure Git on Remote Server
```bash
# In VSCode terminal (remote)
git config --global user.name "<Full Name>"
git config --global user.email "<email@example.com>"
# Test Git connectivity
git clone https://github.com/<username>/<repo>.git
```
## Part 5: Remote Development Workflows
### 1. File Management
**File Explorer:**
- Shows remote server's file system
- Create, edit, delete files directly
- Drag and drop between local and remote (limited)
**File Transfer:**
```bash
# Upload files to remote (from local terminal)
scp localfile.txt <username>@<tailscale-ip>:/home/<username>/
# Download files from remote
scp <username>@<tailscale-ip>:/remote/path/file.txt ./local/path/
```
### 2. Terminal Usage
**Integrated Terminal:**
- Press **Ctrl+`** to open terminal
- Runs directly on remote server
- Multiple terminals supported
- Full shell access (bash, zsh, etc.)
**Common Remote Terminal Commands:**
```bash
# Check system resources
htop
df -h
free -h
# Install packages
sudo apt update
sudo apt install nodejs npm
# Start services
sudo systemctl start nginx
sudo docker-compose up -d
```
### 3. Port Forwarding
**Automatic Port Forwarding:**
VSCode automatically detects and forwards common development ports.
**Manual Port Forwarding:**
1. Open **Ports** tab in terminal panel
2. Click **"Forward a Port"**
3. Enter port number (e.g., 3000, 8080, 5000)
4. Access via `http://localhost:port` on the local machine
**Example: Web Development**
```bash
# Start a web server on remote (port 3000)
npm start
# VSCode automatically suggests forwarding port 3000
# Access at http://localhost:3000 on the local machine
```
### 4. Debugging Remote Applications
**Python Debugging:**
```json
// .vscode/launch.json on remote server
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
```
**Node.js Debugging:**
```json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Program",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/app.js"
}
]
}
```
## Part 6: Advanced Configuration
### 1. Workspace Settings
Create remote-specific settings:
```json
// .vscode/settings.json (on remote server)
{
"python.defaultInterpreterPath": "/usr/bin/python3",
"terminal.integrated.shell.linux": "/bin/bash",
"files.autoSave": "afterDelay",
"editor.formatOnSave": true,
"remote.SSH.remotePlatform": {
"node1": "linux"
}
}
```
### 2. Multi-Server Management
**Switch Between Servers:**
1. Click remote indicator (bottom-left)
2. Select **"Connect to Host..."**
3. Choose a different server
**Compare Files Across Servers:**
1. Open file from server A
2. Connect to server B in new window
3. Open corresponding file
4. Use **"Compare with..."** command
### 3. Sync Configuration
**Settings Sync:**
1. Enable Settings Sync in VSCode
2. Settings, extensions, and keybindings sync to remote
3. Consistent experience across all servers
## Part 7: Project-Specific Setups
### 1. Python Development
```bash
# On remote server
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install packages
pip install flask django requests
# VSCode automatically detects Python interpreter
```
**VSCode Python Configuration:**
```json
// .vscode/settings.json
{
"python.defaultInterpreterPath": "./venv/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true
}
```
### 2. Node.js Development
```bash
# On remote server
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Create project
mkdir myapp && cd myapp
npm init -y
npm install express
```
### 3. Docker Development
```bash
# On remote server
# Install Docker (if not already done via Ansible)
sudo apt install docker.io docker-compose
sudo usermod -aG docker $USER
# Create Dockerfile
cat > Dockerfile << EOF
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
EOF
```
**VSCode Docker Integration:**
- Install Docker extension on remote
- Right-click Dockerfile → "Build Image"
- Manage containers from VSCode interface
## Part 8: Troubleshooting Guide
### Common Connection Issues
**Problem: "Could not establish connection to remote host"**
**Solutions:**
```bash
# Check Tailscale connectivity
tailscale status
ping <tailscale-ip>
# Test SSH manually
ssh <username>@<tailscale-ip>
# Check SSH config syntax
ssh -T node1
```
**Problem: "Permission denied (publickey)"**
**Solutions:**
```bash
# Check SSH key permissions
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/config
# Verify SSH agent
ssh-add ~/.ssh/id_rsa
ssh-add -l
# Test SSH connection verbosely
ssh -v <username>@<tailscale-ip>
```
**Problem: "Host key verification failed"**
**Solutions:**
```bash
# Remove old host key
ssh-keygen -R <tailscale-ip>
# Or disable host key checking (less secure)
# Add to SSH config:
# StrictHostKeyChecking no
```
### VSCode-Specific Issues
**Problem: Extensions not working on remote**
**Solutions:**
1. Install extensions specifically for the remote server
2. Check extension compatibility with remote development
3. Reload VSCode window: Ctrl+Shift+P → "Developer: Reload Window"
**Problem: Slow performance**
**Solutions:**
- Use `.vscode/settings.json` to exclude large directories:
```json
{
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/objects/**": true,
"**/dist/**": true
}
}
```
**Problem: Terminal not starting**
**Solutions:**
```bash
# Check shell path in remote settings
"terminal.integrated.shell.linux": "/bin/bash"
# Or let VSCode auto-detect
"terminal.integrated.defaultProfile.linux": "bash"
```
### Network and Performance Issues
**Problem: Connection timeouts**
**Solutions:**
Add to SSH config:
```
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
```
**Problem: File transfer slow**
**Solutions:**
- Use `.vscodeignore` to exclude unnecessary files
- Compress large files before transfer
- Use `rsync` for large file operations:
```bash
rsync -avz --progress localdir/ <username>@<tailscale-ip>:remotedir/
```
## Part 9: Best Practices
### Security Best Practices
1. **Use SSH keys, never passwords**
2. **Keep SSH agent secure**
3. **Regular security updates on remote servers**
4. **Use VSCode's secure connection verification**
### Performance Optimization
1. **Exclude unnecessary files**:
```json
// .vscode/settings.json
{
"files.watcherExclude": {
"**/node_modules/**": true,
"**/.git/**": true,
"**/dist/**": true,
"**/build/**": true
},
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.code-search": true
}
}
```
2. **Use remote workspace for large projects**
3. **Close unnecessary windows and extensions**
4. **Use efficient development workflows**
### Development Workflow
1. **Use version control effectively**:
```bash
# Always work in Git repositories
git status
git add .
git commit -m "feature: add new functionality"
git push origin main
```
2. **Environment separation**:
```bash
# Development
ssh node1
cd /home/<username>/dev-projects
# Production
ssh node2
cd /opt/production-apps
```
3. **Backup important work**:
```bash
# Regular backups via Git
git push origin main
# Or manual backup
scp -r <username>@<tailscale-ip>:/important/project ./backup/
```
## Part 10: Team Collaboration
### Shared Development Servers
**SSH Config for Team:**
```
# Shared development server
Host team-dev
HostName <tailscale-ip>
User <team-user>
IdentityFile ~/.ssh/team_dev_key
ForwardAgent yes
# Personal development
Host my-dev
HostName <tailscale-ip>
User <username>
IdentityFile ~/.ssh/id_rsa
```
### Project Structure
```
/opt/projects/
├── project-a/
│ ├── dev/ # Development branch
│ ├── staging/ # Staging environment
│ └── docs/ # Documentation
├── project-b/
└── shared-tools/ # Common utilities
```
### Access Management
```bash
# Create shared project directory
sudo mkdir -p /opt/projects
sudo chown -R :developers /opt/projects
sudo chmod -R g+w /opt/projects
# Add users to developers group
sudo usermod -a -G developers <username>
```
## Quick Reference
### Essential VSCode Remote Commands
```bash
# Command Palette shortcuts
Ctrl+Shift+P → "Remote-SSH: Connect to Host..."
Ctrl+Shift+P → "Remote-SSH: Open SSH Configuration File..."
Ctrl+Shift+P → "Remote-SSH: Kill VS Code Server on Host..."
# Terminal
Ctrl+` → Open integrated terminal
Ctrl+Shift+` → Create new terminal
# File operations
Ctrl+O → Open file
Ctrl+S → Save file
Ctrl+Shift+E → Focus file explorer
```
### SSH Connection Quick Test
```bash
# Test connectivity
ssh -T node1
# Connect with verbose output
ssh -v <username>@<tailscale-ip>
# Check SSH config
ssh -F ~/.ssh/config node1
```
### Port Forwarding Commands
```bash
# Manual port forwarding
ssh -L 3000:localhost:3000 <username>@<tailscale-ip>
# Background tunnel
ssh -f -N -L 8080:localhost:80 <username>@<tailscale-ip>
```
## Conclusion
This remote development setup provides:
- **Full development environment** on remote servers
- **Seamless file access** and editing capabilities
- **Integrated debugging** and terminal access
- **Port forwarding** for web development
- **Extension ecosystem** available remotely
- **Secure connections** through Tailscale network
The combination of VSCode Remote Development with Tailscale networking creates a powerful, flexible development environment that works from anywhere while maintaining security and performance.
Whether developing Python applications, Node.js services, or managing Docker containers, this setup provides a professional remote development experience that rivals local development while leveraging the power and resources of remote servers.

View File

@@ -1,485 +0,0 @@
# Getting Started
Welcome to Changemaker-Lite! You're about to reclaim your digital sovereignty and stop feeding your secrets to corporations. This guide will help you set up your own political infrastructure that you actually own and control.
This documentation is broken into a few sections, which you can see in the navigation bar to the left:
- **Build:** Instructions on how to build the cm-lite on your own hardware
- **Services:** Overview of all the services that are installed when you install cm-lite
- **Configuration:** Information on how to configure all the services that you install in cm-lite
- **Manuals:** Manuals on how to use the applications inside cm-lite (with videos!)
Of course, everything is also searachable, so if you want to find something specific, just use the search bar at the top right.
If you come across anything that is unclear, please open an issue in the [Git Repository](https://gitea.bnkops.com/admin/changemaker.lite), reach out to us at [admin@thebunkerops.ca](mailto:admin@thebunkerops.ca), or edit it yourself by clicking the pencil icon at the top right of each page.
## Quick Start
### Build Changemaker-Lite
```bash
# Clone the repository
git clone https://gitea.bnkops.com/admin/changemaker.lite
cd changemaker.lite
```
!!! warning "Cloudflare Credentials"
The config.sh script will ask you for your optional Cloudflare credentials to get started. You can find more information on how to find this in the [Cloudlflare Configuration](../config/cloudflare-config.md)
```
# Configure environment (creates .env file)
./config.sh
```
```
# Start all services
docker compose up -d
```
### Optional - Site Builld
If you want to have your site prepared for launch, you can now proceed with reseting the site build. See [Build Site](../build/site.md) for more detials.
### Deploy
!!! note "Cloudflare"
Right now, we suggest deploying using Cloudflare for simplicity and protections against 99% of surface level attacks to digital infrastructure. If you want to avoid using this service, we recommend checking out [Pagolin](https://github.com/fosrl/pangolin) as a drop in replacement.
For secure public access, use the production deployment script:
```bash
./start-production.sh
```
### Map
Map is the canvassing application that is custom view of nocodb data. Map is best built **after production deployment** to reduce duplicate build efforts.
Instructions on how to build the map are available in the [map manual](../build/map.md) in the build directory.
#### Quick Start for Map
Get your NocoDB API token and URL, update the .env file in the map directory, and then run:
```
cd map
chmod +x build-nocodb.sh # builds the nocodb tables
./build-nocodb.sh
```
Copy the urls of the newly created nocodb views and update the .env file in the map directory with them, and then run:
```
cd map
docker compose up -d
```
You Map instance will be available at [http://localhost:3000](http://localhost:3000) or on the domain you set up during production deployment.
## Why Changemaker Lite?
Before we dive into the technical setup, let's be clear about what you're doing here:
!!! quote "The Reality"
**If you do politics, who is reading your secrets?** Every corporate platform you use is extracting your power, selling your data, and building profiles on your community. It's time to break free.
### What You're Getting
- **Data Sovereignty**: Your data stays on your servers
- **Cost Savings**: $50/month instead of $2,000+/month for corporate solutions
- **Community Control**: Technology that serves movements, not shareholders
- **Trans Liberation**: Tools built with radical politics and care
### What You're Leaving Behind
- ❌ Corporate surveillance and data extraction
- ❌ Escalating subscription fees and vendor lock-in
- ❌ Algorithmic manipulation of your community
- ❌ Terms of service that can silence you anytime
---
## System Requirements
### Operating System
- **Ubuntu 24.04 LTS (Noble Numbat)** - Recommended and tested
!!! note "Getting Started on Ubuntu"
Want some help getting started with a baseline buildout for a Ubuntu server? You can use our [BNKops Server Build Script](./server.md)
- Other Linux distributions with systemd support
- WSL2 on Windows (limited functionality)
- Mac OS
!!! tip "New to Linux?"
Consider [Linux Mint](https://www.linuxmint.com/) - it looks like Windows but opens the door to true digital freedom.
### Hardware Requirements
- **CPU**: 2+ cores (4+ recommended)
- **RAM**: 4GB minimum (8GB recommended)
- **Storage**: 20GB+ available disk space
- **Network**: Stable internet connection
!!! info "Cloud Hosting"
You can run this on a VPS from providers like Hetzner, DigitalOcean, or Linode for ~$20/month.
### Software Prerequisites
Ensure the following software is installed on your system. The [BNKops Server Build Script](./server.md) can help set these up if you're on Ubuntu.
1. **Docker Engine** (24.0+)
```bash
# Install Docker
curl -fsSL https://get.docker.com | sudo sh
# Add your user to docker group
sudo usermod -aG docker $USER
# Log out and back in for group changes to take effect
```
2. **Docker Compose** (v2.20+)
```bash
# Verify Docker Compose v2 is installed
docker compose version
```
3. **Essential Tools**
```bash
# Install required packages
sudo apt update
sudo apt install -y git curl jq openssl
```
## Installation
### 1. Clone Repository
```bash
git clone https://gitea.bnkops.com/admin/changemaker.lite
cd changemaker.lite
```
### 2. Run Configuration Wizard
The `config.sh` script will guide you through the initial setup:
```bash
./config.sh
```
This wizard will:
- ✅ Create a `.env` file with secure defaults
- ✅ Scan for available ports to avoid conflicts
- ✅ Set up your domain configuration
- ✅ Generate secure passwords for databases
- ✅ Configure Cloudflare credentials (optional)
- ✅ Update all configuration files with your settings
#### Configuration Options
During setup, you'll be prompted for:
1. **Domain Name**: Your primary domain (e.g., `example.com`)
2. **Cloudflare Settings** (optional):
- API Token
- Zone ID
- Account ID
3. **Admin Credentials**:
- Listmonk admin email and password
- n8n admin email and password
### 3. Start Services
Launch all services with Docker Compose:
```bash
docker compose up -d
```
Wait for services to initialize (first run may take 5-10 minutes):
```bash
# Watch container status
docker compose ps
# View logs
docker compose logs -f
```
### 4. Verify Installation
Check that all services are running:
```bash
docker compose ps
```
Expected output should show all services as "Up":
- code-server-changemaker
- listmonk_app
- listmonk_db
- mkdocs-changemaker
- mkdocs-site-server-changemaker
- n8n-changemaker
- nocodb
- root_db
- homepage-changemaker
- gitea_changemaker
- gitea_mysql_changemaker
- mini-qr
## Local Access
Once services are running, access them locally:
### 🏠 Homepage Dashboard
- **URL**: [http://localhost:3010](http://localhost:3010)
- **Purpose**: Central hub for all services
- **Features**: Service status, quick links, monitoring
### 💻 Development Tools
- **Code Server**: [http://localhost:8888](http://localhost:8888) — VS Code in browser
- **Gitea**: [http://localhost:3030](http://localhost:3030) — Git repository management
- **MkDocs Dev**: [http://localhost:4000](http://localhost:4000) — Live documentation preview
- **MkDocs Prod**: [http://localhost:4001](http://localhost:4001) — Built documentation
### 📧 Communication
- **Listmonk**: [http://localhost:9000](http://localhost:9000) — Email campaigns
_Login with credentials set during configuration_
### 🔄 Automation & Data
- **n8n**: [http://localhost:5678](http://localhost:5678) — Workflow automation
_Login with credentials set during configuration_
- **NocoDB**: [http://localhost:8090](http://localhost:8090) — No-code database
### 🛠️ Interactive Tools
- **Mini QR**: [http://localhost:8089](http://localhost:8089) — QR code generator
## Map
!!! warning "Map"
Map is the canvassing application that is custom view of nocodb data. Map is best built **after production deployment** to reduce duplicate build efforts.
### [Map Manual](map.md)
## Production Deployment
### Deploy with Cloudflare Tunnels
For secure public access, use the production deployment script:
```bash
./start-production.sh
```
This script will:
1. Install and configure `cloudflared`
2. Create a Cloudflare tunnel
3. Set up DNS records automatically
4. Configure access policies
5. Create a systemd service for persistence
### What Happens During Production Setup
1. **Cloudflare Authentication**: Browser-based login to Cloudflare
2. **Tunnel Creation**: Secure tunnel named `changemaker-lite`
3. **DNS Configuration**: Automatic CNAME records for all services
4. **Access Policies**: Email-based authentication for sensitive services
5. **Service Installation**: Systemd service for automatic startup
### Production URLs
After successful deployment, services will be available at:
**Public Services**:
- `https://yourdomain.com` - Main documentation site
- `https://listmonk.yourdomain.com` - Email campaigns
- `https://docs.yourdomain.com` - Documentation preview
- `https://n8n.yourdomain.com` - Automation platform
- `https://db.yourdomain.com` - NocoDB
- `https://git.yourdomain.com` - Gitea
- `https://map.yourdomain.com` - Map viewer
- `https://qr.yourdomain.com` - QR generator
**Protected Services** (require authentication):
- `https://homepage.yourdomain.com` - Dashboard
- `https://code.yourdomain.com` - Code Server
## Configuration Management
### Environment Variables
Key settings in `.env` file:
```env
# Domain Configuration
DOMAIN=yourdomain.com
BASE_DOMAIN=https://yourdomain.com
# Service Ports (automatically assigned to avoid conflicts)
HOMEPAGE_PORT=3010
CODE_SERVER_PORT=8888
LISTMONK_PORT=9000
MKDOCS_PORT=4000
MKDOCS_SITE_SERVER_PORT=4001
N8N_PORT=5678
NOCODB_PORT=8090
GITEA_WEB_PORT=3030
GITEA_SSH_PORT=2222
MAP_PORT=3000
MINI_QR_PORT=8089
# Cloudflare (for production)
CF_API_TOKEN=your_token
CF_ZONE_ID=your_zone_id
CF_ACCOUNT_ID=your_account_id
```
### Reconfigure Services
To update configuration:
```bash
# Re-run configuration wizard
./config.sh
# Restart services
docker compose down && docker compose up -d
```
## Common Tasks
### Service Management
```bash
# View all services
docker compose ps
# View logs for specific service
docker compose logs -f [service-name]
# Restart a service
docker compose restart [service-name]
# Stop all services
docker compose down
# Stop and remove all data (CAUTION!)
docker compose down -v
```
### Backup Data
```bash
# Backup all volumes
docker run --rm -v changemaker_listmonk-data:/data -v $(pwd):/backup alpine tar czf /backup/listmonk-backup.tar.gz -C /data .
# Backup configuration
tar czf configs-backup.tar.gz configs/
# Backup documentation
tar czf docs-backup.tar.gz mkdocs/docs/
```
### Update Services
```bash
# Pull latest images
docker compose pull
# Recreate containers with new images
docker compose up -d
```
## Troubleshooting
### Port Conflicts
If services fail to start due to port conflicts:
1. Check which ports are in use:
```bash
sudo ss -tulpn | grep LISTEN
```
2. Re-run configuration to get new ports:
```bash
./config.sh
```
3. Or manually edit `.env` file and change conflicting ports
### Permission Issues
Fix permission problems:
```bash
# Get your user and group IDs
id -u # User ID
id -g # Group ID
# Update .env file with correct IDs
USER_ID=1000
GROUP_ID=1000
# Restart services
docker compose down && docker compose up -d
```
### Service Won't Start
Debug service issues:
```bash
# Check detailed logs
docker compose logs [service-name] --tail 50
# Check container status
docker ps -a
# Inspect container
docker inspect [container-name]
```
### Cloudflare Tunnel Issues
```bash
# Check tunnel service status
sudo systemctl status cloudflared-changemaker
# View tunnel logs
sudo journalctl -u cloudflared-changemaker -f
# Restart tunnel
sudo systemctl restart cloudflared-changemaker
```
## Next Steps
Now that your Changemaker Lite instance is running:
1. **Set up Listmonk** - Configure SMTP and create your first campaign
2. **Create workflows** - Build automations in n8n
3. **Import data** - Set up your NocoDB databases
4. **Configure map** - Add location data for the map viewer
5. **Write documentation** - Start creating content in MkDocs
6. **Set up Git** - Initialize repositories in Gitea
## Getting Help
- Check the [Services](../services/index.md) documentation for detailed guides
- Review container logs for specific error messages
- Ensure all prerequisites are properly installed
- Verify your domain DNS settings for production deployment

View File

@@ -1,328 +0,0 @@
# Influence Build Guide
Influence is BNKops campaign tool for connecting Alberta residents with their elected representatives across all levels of government.
!!! info "Complete Configuration"
For detailed configuration, usage instructions, and troubleshooting, see the main [Influence README](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/README.MD).
!!! tip "Email Testing"
The application includes MailHog integration for safe email testing during development. All test emails are caught locally and never sent to actual representatives.
## Prerequisites
- Docker and Docker Compose installed
- NocoDB instance with API access
- SMTP email configuration (or use MailHog for testing)
- Domain name (optional but recommended for production)
## Quick Build Process
### 1. Get NocoDB API Token
1. Login to your NocoDB instance
2. Click user icon → **Account Settings****API Tokens**
3. Create new token with read/write permissions
4. Copy the token for the next step
### 2. Configure Environment
Navigate to the influence directory and create your environment file:
```bash
cd influence
cp example.env .env
```
Edit the `.env` file with your configuration:
#### Development Mode Configuration
For development and testing, use MailHog to catch emails:
```env
# Development Mode
NODE_ENV=development
EMAIL_TEST_MODE=true
# MailHog SMTP (for development)
SMTP_HOST=mailhog
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=test
SMTP_PASS=test
SMTP_FROM_EMAIL=dev@albertainfluence.local
SMTP_FROM_NAME="BNKops Influence Campaign (DEV)"
# Email Testing
TEST_EMAIL_RECIPIENT=developer@example.com
```
### 3. Auto-Create Database Structure
Run the build script to create required NocoDB tables:
```bash
chmod +x scripts/build-nocodb.sh
./scripts/build-nocodb.sh
```
This creates six tables:
- **Campaigns** - Campaign configurations with email templates and settings
- **Campaign Emails** - Tracking of all emails sent through campaigns
- **Representatives** - Cached representative data by postal code
- **Email Logs** - System-wide email delivery logs
- **Postal Codes** - Canadian postal code geolocation data
- **Users** - Admin authentication and access control
### 4. Build and Deploy
Build the Docker image and start the application:
```bash
# Build the Docker image
docker compose build
# Start the application (includes MailHog in development)
docker compose up -d
```
## Verify Installation
1. Check container status:
```bash
docker compose ps
```
2. View logs:
```bash
docker compose logs -f app
```
3. Access the application:
- **Main App**: http://localhost:3333
- **Admin Panel**: http://localhost:3333/admin.html
- **Email Testing** (dev): http://localhost:3333/email-test.html
- **MailHog UI** (dev): http://localhost:8025
## Initial Setup
### 1. Create Admin User
Access the admin panel at `/admin.html` and create your first administrator account.
### 2. Create Your First Campaign
1. Login to the admin panel
2. Click **"Create Campaign"**
3. Configure basic settings:
- Campaign title and description
- Email subject and body template
- Upload cover photo (optional)
4. Set campaign options:
- ✅ Allow SMTP Email - Enable server-side sending
- ✅ Allow Mailto Link - Enable browser-based mailto
- ✅ Collect User Info - Request name and email
- ✅ Show Email Count - Display engagement metrics
- ✅ Allow Email Editing - Let users customize message
5. Select target government levels (Federal, Provincial, Municipal, School Board)
6. Set status to **Active** to make campaign public
7. Click **"Create Campaign"**
### 3. Test Representative Lookup
1. Visit the homepage
2. Enter an Alberta postal code (e.g., T5N4B8)
3. View representatives at all government levels
4. Test email sending functionality
## Development Workflow
### Email Testing Interface
Access the email testing interface at `/email-test.html` (requires admin login):
**Features:**
- 📧 **Quick Test** - Send test email with one click
- 👁️ **Email Preview** - Preview email formatting before sending
- ✏️ **Custom Composition** - Test with custom subject and message
- 📊 **Email Logs** - View all sent emails with filtering
- 🔧 **SMTP Diagnostics** - Test connection and troubleshoot
### MailHog Web Interface
Access MailHog at http://localhost:8025 to:
- View all caught emails during development
- Inspect email content, headers, and formatting
- Search and filter test emails
- Verify emails never leave your local environment
### Switching to Production
When ready to deploy to production:
1. Update `.env` with production SMTP settings:
```env
EMAIL_TEST_MODE=false
NODE_ENV=production
SMTP_HOST=smtp.your-provider.com
SMTP_USER=your-real-email@domain.com
SMTP_PASS=your-real-password
```
2. Restart the application:
```bash
docker compose restart
```
## Key Features
### Representative Lookup
- Search by Alberta postal code (T prefix)
- Display federal MPs, provincial MLAs, municipal representatives
- Smart caching with NocoDB for fast performance
- Graceful fallback to Represent API when cache unavailable
### Campaign System
- Create unlimited advocacy campaigns
- Upload cover photos for campaign pages
- Customizable email templates
- Optional user information collection
- Toggle email count display for engagement metrics
- Multi-level government targeting
### Email Integration
- SMTP email sending with delivery confirmation
- Mailto link support for browser-based email
- Comprehensive email logging
- Rate limiting for API protection
- Test mode for safe development
## API Endpoints
### Public Endpoints
- `GET /` - Homepage with representative lookup
- `GET /campaign/:slug` - Individual campaign page
- `GET /api/public/campaigns` - List active campaigns
- `GET /api/representatives/by-postal/:postalCode` - Find representatives
- `POST /api/emails/send` - Send campaign email
### Admin Endpoints (Authentication Required)
- `GET /admin.html` - Campaign management dashboard
- `GET /email-test.html` - Email testing interface
- `POST /api/emails/preview` - Preview email without sending
- `POST /api/emails/test` - Send test email
- `GET /api/test-smtp` - Test SMTP connection
## Maintenance Commands
### Update Application
```bash
docker compose down
git pull origin main
docker compose build
docker compose up -d
```
### Development Mode
```bash
cd app
npm install
npm run dev
```
### View Logs
```bash
# Follow application logs
docker compose logs -f app
# View MailHog logs (development)
docker compose logs -f mailhog
```
### Database Backup
```bash
# Backup is handled through NocoDB
# Access NocoDB admin panel to export tables
```
### Health Check
```bash
curl http://localhost:3333/api/health
```
## Troubleshooting
### NocoDB Connection Issues
- Verify `NOCODB_API_URL` and `NOCODB_API_TOKEN` in `.env`
- Run `./scripts/build-nocodb.sh` to ensure tables exist
- Application works without NocoDB (API fallback mode)
### Email Not Sending
- In development: Check MailHog UI at http://localhost:8025
- Verify SMTP credentials in `.env`
- Use `/email-test.html` interface for diagnostics
- Check email logs via admin panel
- Review `docker compose logs -f app` for errors
### No Representatives Found
- Ensure postal code starts with 'T' (Alberta only)
- Try different postal code format (remove spaces)
- Check Represent API status: `curl http://localhost:3333/api/test-represent`
- Review application logs for API errors
### Campaign Not Appearing
- Verify campaign status is set to "Active"
- Check campaign configuration in admin panel
- Clear browser cache and reload homepage
- Review console for JavaScript errors
## Production Deployment
### Environment Configuration
```env
NODE_ENV=production
EMAIL_TEST_MODE=false
PORT=3333
# Use production SMTP settings
SMTP_HOST=smtp.your-provider.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-production-email@domain.com
SMTP_PASS=your-production-password
```
### Docker Production
```bash
# Build and start in production mode
docker compose -f docker-compose.yml up -d --build
# View logs
docker compose logs -f app
# Monitor health
watch curl http://localhost:3333/api/health
```
### Monitoring
- Health check endpoint: `/api/health`
- Email logs via admin panel
- NocoDB integration status in logs
- Rate limiting metrics in application logs
## Security Considerations
- 🔒 Always use strong passwords for admin accounts
- 🔒 Enable HTTPS in production (use reverse proxy)
- 🔒 Rotate SMTP credentials regularly
- 🔒 Monitor email logs for suspicious activity
- 🔒 Set appropriate rate limits based on expected traffic
- 🔒 Keep NocoDB API tokens secure and rotate periodically
- 🔒 Use `EMAIL_TEST_MODE=false` only in production
## Support
For detailed configuration, troubleshooting, and usage instructions, see:
- [Main Influence README](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/README.MD)
- [Campaign Settings Guide](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/CAMPAIGN_SETTINGS_GUIDE.md)
- [Files Explainer](https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/influence/files-explainer.md)

View File

@@ -1,217 +0,0 @@
# Map Build Guide
Map is BNKops canvassing application built for community organizing and door-to-door canvassing.
!!! info "Complete Configuration"
For detailed configuration, usage instructions, and troubleshooting, see the [Map Configuration Guide](../config/map.md).
!!! warning "Clean NocoDB"
Currently the way to get a good result is to ensure the target nocodb database is empty. You can do this by deleting all bases. The script should still work with other volumes however may insert tables into odd locations; still debugging. Again, see config if needing to do manually.
## Prerequisites
- Docker and Docker Compose installed
- NocoDB instance with API access
- Domain name (optional but recommended for production)
## Quick Build Process
### 1. Get NocoDB API Token
1. Login to your NocoDB instance
2. Click user icon → **Account Settings****API Tokens**
3. Create new token with read/write permissions
4. Copy the token for the next step
### 2. Configure Environment
Edit the `.env` file in the `map/` directory:
```bash
cd map
```
Update your `.env` file with your NocoDB details, specifically the instance and api token:
```env
NOCODB_API_URL=[change me]
NOCODB_API_TOKEN=[change me]
# NocoDB View URL is the URL to your NocoDB view where the map data is stored.
NOCODB_VIEW_URL=[change me]
# NOCODB_LOGIN_SHEET is the URL to your NocoDB login sheet.
NOCODB_LOGIN_SHEET=[change me]
# NOCODB_SETTINGS_SHEET is the URL to your NocoDB settings sheet.
NOCODB_SETTINGS_SHEET=[change me]
# NOCODB_SHIFTS_SHEET is the URL to your shifts sheet.
NOCODB_SHIFTS_SHEET=[change me]
# NOCODB_SHIFT_SIGNUPS_SHEET is the URL to your NocoDB shift signups sheet where users can add their own shifts.
NOCODB_SHIFT_SIGNUPS_SHEET=[change me]
# NOCODB_CUTS_SHEET is the URL to your NocoDB Cuts sheet.
NOCODB_CUTS_SHEET=[change me]
DOMAIN=[change me]
# MkDocs Integration
MKDOCS_URL=[change me]
MKDOCS_SEARCH_URL=[change me]
MKDOCS_SITE_SERVER_PORT=4002
# Server Configuration
PORT=3000
NODE_ENV=production
# Session Secret (IMPORTANT: Generate a secure random string for production)
SESSION_SECRET=[change me]
# Map Defaults (Edmonton, Alberta, Canada)
DEFAULT_LAT=53.5461
DEFAULT_LNG=-113.4938
DEFAULT_ZOOM=11
# Optional: Map Boundaries (prevents users from adding points outside area)
# BOUND_NORTH=53.7
# BOUND_SOUTH=53.4
# BOUND_EAST=-113.3
# BOUND_WEST=-113.7
# Cloudflare Settings
TRUST_PROXY=true
COOKIE_DOMAIN=[change me]
# Update NODE_ENV to production for HTTPS
NODE_ENV=production
# Add allowed origin
ALLOWED_ORIGINS=[change me]
# SMTP Configuration
SMTP_HOST=[change me]
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=[change me]
SMTP_PASS=[change me]
EMAIL_FROM_NAME="[change me]"
EMAIL_FROM_ADDRESS=[change me]
# App Configuration
APP_NAME="[change me]"
# Listmonk Configuration
LISTMONK_API_URL=[change me]
LISTMONK_USERNAME=[change me]
LISTMONK_PASSWORD=[change me]
LISTMONK_SYNC_ENABLED=true
LISTMONK_INITIAL_SYNC=false # Set to true only for first run to sync existing data
```
### 3. Auto-Create Database Structure
Run the build script to create required tables:
```bash
chmod +x build-nocodb.sh
./build-nocodb.sh
```
This creates three tables:
- **Locations** - Main map data with geo-location, contact info, support levels
- **Login** - User authentication (email, name, admin flag)
- **Settings** - Admin configuration and QR codes
### 4. Get Table URLs
After the script completes:
1. Login to your NocoDB instance
2. Navigate to your project ("Map Viewer Project")
3. Copy the view URLs for each table from your browser address bar
4. URLs should look like: `https://your-nocodb.com/dashboard/#/nc/project-id/table-id`
### 5. Update Environment with URLs
Edit your `.env` file and add the table URLs:
```env
# NocoDB View URL is the URL to your NocoDB view where the map data is stored.
NOCODB_VIEW_URL=[change me]
# NOCODB_LOGIN_SHEET is the URL to your NocoDB login sheet.
NOCODB_LOGIN_SHEET=[change me]
# NOCODB_SETTINGS_SHEET is the URL to your NocoDB settings sheet.
NOCODB_SETTINGS_SHEET=[change me]
# NOCODB_SHIFTS_SHEET is the URL to your shifts sheet.
NOCODB_SHIFTS_SHEET=[change me]
# NOCODB_SHIFT_SIGNUPS_SHEET is the URL to your NocoDB shift signups sheet where users can add their own shifts.
NOCODB_SHIFT_SIGNUPS_SHEET=[change me]
# NOCODB_CUTS_SHEET is the URL to your NocoDB Cuts sheet.
NOCODB_CUTS_SHEET=[change me]
```
### 6. Build and Deploy
Build the Docker image and start the application:
```bash
# Build the Docker image
docker-compose build
# Start the application
docker-compose up -d
```
## Verify Installation
1. Check container status:
```bash
docker-compose ps
```
2. View logs:
```bash
docker-compose logs -f map-viewer
```
3. Access the application at `http://localhost:3000`
## Quick Start
1. **Login**: Use an email from your Login table
2. **Add Locations**: Click on the map to add new locations
3. **Admin Panel**: Admin users can access `/admin.html` for configuration
4. **Walk Sheets**: Generate printable canvassing forms with QR codes
## Maintenance Commands
### Update Application
```bash
docker-compose down
git pull origin main
docker-compose build
docker-compose up -d
```
### Development Mode
```bash
cd app
npm install
npm run dev
```
### Health Check
```bash
curl http://localhost:3000/health
```
## Support
For detailed configuration, troubleshooting, and usage instructions, see the [Map Configuration Guide](../config/map.md).

View File

@@ -1,148 +0,0 @@
# BNKops Server Build
Purpose: a Ubuntu server build-out for general application
---
This documentation is a overview of the full build out for a server OS and baseline for running Changemaker-lite. It is a manual to re-install this server on any machine.
All of the following systems are free and the majority are open source.
## [Ubuntu](https://ubuntu.com/) OS
_Ubuntu_ is a Linux distribution derived from Debian and composed mostly of free and open-source software.
### [Install Ubuntu](https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview)
### Post Install
Post installation, run update:
```
sudo apt update
```
```
sudo apt upgrade
```
### Configuration
Further configurations:
- User profile was updated to Automatically Login
- Remote Desktop, Sharing, and Login have all been enabled.
- Default system settings have been set to dark mode.
## [VSCode Insiders](https://code.visualstudio.com/insiders/)
Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle.
### Install Using App Centre
## [Obsidian](https://obsidian.md/)
The free and flexible app for your private thoughts.
### Install Using App Center
## [Curl](https://curl.se/)
command line tool and library for transferring data with URLs (since 1998)
### Install
```
sudo apt install curl
```
## [Glances](https://github.com/nicolargo/glances)
Glances an Eye on your system. A top/htop alternative for GNU/Linux, BSD, Mac OS and Windows operating systems.
### Install
```
sudo snap install glances
```
## [Syncthing](https://syncthing.net/)
Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how its transmitted over the internet.
### Install
```
# Add the release PGP keys:
sudo mkdir -p /etc/apt/keyrings
sudo curl -L -o /etc/apt/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
```
```
# Add the "stable" channel to your APT sources:
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" | sudo tee /etc/apt/sources.list.d/syncthing.list
```
```
# Update and install syncthing:
sudo apt-get update
sudo apt-get install syncthing
```
### Post Install
Run syncthing as a system service.
```
sudo systemctl start syncthing@yourusername
```
```
sudo systemctl enable syncthing@yourusername
```
## [Docker](https://www.docker.com/)
Docker helps developers build, share, run, and verify applications anywhere — without tedious environment configuration or management.
```
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
```
```
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
### Update Users
```
sudo groupadd docker
```
```
sudo usermod -aG docker $USER
```
```
newgrp docker
```
### Enable on Boot
```
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
```
## [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
Connect, protect, and build everywhere. We make websites, apps, and networks faster and more secure. Our developer platform is the best place to build modern apps and deliver AI initiatives.
```
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
```
```
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
```
```
sudo apt-get update && sudo apt-get install cloudflared
```
### Post Install
Login to Cloudflare
```
cloudflared login
```
### Configuration
The `./config.sh` and `./start-production.sh` scripts will properly configure a Cloudflare tunnel and service to put your system online. More info in the [Cloudflare Configuration.](../config/cloudflare-config.md)
## [Pandoc](https://pandoc.org/)
If you need to convert files from one markup format into another, pandoc is your swiss-army knife.
```
sudo apt install pandoc
```

View File

@@ -1,107 +0,0 @@
# Building the Site with MkDocs Material
Welcome! This guide will help you get started building and customizing your site using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
---
## Reset Site
You can read through all the BNKops cmlite documentation already in your docs folder or you can reset your docs folder to a baseline to start and read more manuals here. To reset docs folder to baseline, run the following:
```bash
./reset-site.sh
```
## 🚀 How to Build Your Site (Step by Step)
1. **Open your Coder instance.**
For example: coder.yourdomain.com
2. **Go to the mkdocs folder:**
In the terminal (for a new terminal press Crtl - Shift - ~), type:
```sh
cd mkdocs
```
3. **Build the site:**
Type:
```sh
mkdocs build
```
This creates the static website from your documents and places them in the `mkdocs/site` directory.
**Preview your site locally:**
Visit [localhost:4000](localhost:4000) for local development or `live.youdomain.com` to see a public live load.
- All documentation in the `mkdocs/docs` folder is included automatically.
- The site uses the beautiful and easy-to-use Material for MkDocs theme.
[Material for MkDocs Documentation :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/){ .md-button }
!!! note "Build vs Serve"
Your website is built in stages. Any edits to documents in the mkdocs directory are instantly served and visible at [localhost:4000](localhost:4000) or if in production mode live.yourdomain.com. **The live site is not meant as a public access point and will crash if too many requests are made to it**.
Running `mkdocs build` pushes any changes to the `site` directory, which then a ngnix server pushes them to the production server for public access at your root domain (yourdomain.com).
You can think of it as serve/live = draft for personal review and build = save/push to production for the public.
This combination allows for rapid development of documentation while ensuring your live site does not get updated until your content is ready.
---
## 🧹 Resetting the Site
If you want to start fresh:
1. **Delete all folders EXCEPT these folders:**
- `/blog`
- `/javascripts`
- `/hooks`
- `/assets`
- `/stylesheets`
- `/overrides`
2. **Reset the landing page:**
- Open the main `index.md` file and remove everything at the very top (the "front matter").
- *Or* edit `/overrides/home.html` to change the landing page.
3. **Reset the `mkdocs.yml`**
- Open `mkdocs.yml` and delete the `nav` section entirely.
- This action will enable mkdocs to build your site navigation based on file names in the root directory.
---
## 🤖 Using AI to Help Build Your Site
- If you have a [claude.ai](https://claude.ai/) subscription, you can use powerful AI in your Coder terminal to write or rewrite pages, including a new `home.html`.
- All you need to do is open the terminal and type:
```sh
claude
```
- You can also try local AI tools like [Ollama](https://ollama.com/) for on-demand help.
---
## 🛠️ First-Time Setup Tips
- **Navigation:**
Open `mkdocs.yml` and remove the `nav` section to start with a blank menu. Add your own pages as you go.
- **Customize the look:**
Check out the [Material for MkDocs customization guide](https://squidfunk.github.io/mkdocs-material/setup/changing-the-theme/).
- **Live preview:**
Use `mkdocs serve` (see above) to see changes instantly as you edit.
- **Custom files:**
Put your own CSS, JavaScript, or HTML in `/assets`, `/stylesheets`, `/javascripts`, or `/overrides`.
[Quick Start Guide :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/creating-your-site/){ .md-button }
---
## 📚 More Resources
- [MkDocs User Guide :material-arrow-right:](https://www.mkdocs.org/user-guide/){ .md-button }
- [Material for MkDocs Features :material-arrow-right:](https://squidfunk.github.io/mkdocs-material/setup/){ .md-button }
- [BNKops MKdocs Configuration & Customization](../config/mkdocs.md){ .md-button }
---
Happy building!

View File

@@ -1,61 +0,0 @@
# Configure Cloudflare
Cloudflare is the largest DNS routing service on the planet. We use their free service tier to provide Changemaker users with a fast, secure, and reliable way to get online that blocks 99% of surface level attacks and has built in user authenticaion (if you so choose to use it)
## Credentials
The `config.sh` and `start-production.sh` scripts require the following Cloudflare credentials to function properly:
### 1. **Cloudflare API Token**
- **Purpose**: Used to authenticate API requests to Cloudflare for managing DNS records, tunnels, and access policies.
- **Required Permissions**:
- `Zone.DNS` (Read/Write)
- `Account.Cloudflare Tunnel` (Read/Write)
- `Access` (Read/Write)
- **How to Obtain**:
- Log in to your Cloudflare account.
- Go to **My Profile** > **API Tokens** > **Create Token**.
- Use the **Edit zone DNS** template and add **Cloudflare Tunnel** permissions.
### 2. **Cloudflare Zone ID**
- **Purpose**: Identifies the specific DNS zone (domain) in Cloudflare where DNS records will be created.
- **How to Obtain**:
- Log in to your Cloudflare account.
- Select the domain you want to use.
- The Zone ID is displayed in the **Overview** section under **API**.
### 3. **Cloudflare Account ID**
- **Purpose**: Identifies your Cloudflare account for tunnel creation and management.
- **How to Obtain**:
- Log in to your Cloudflare account.
- Go to **My Profile** > **API Tokens**.
- The Account ID is displayed at the top of the page.
### 4. **Cloudflare Tunnel ID** (Optional in config.sh, Required in start-production.sh)
!!! note "Automatic Configuration of Tunnel"
The `start-production.sh` script will automatically create a tunnel and system service for Cloudflare.
- **Purpose**: Identifies the specific Cloudflare Tunnel that will be used to route traffic to your services.
- **How to Obtain**:
- This is automatically generated when you create a tunnel using `cloudflared tunnel create` or via the Cloudflare dashboard.
- The start-production.sh script will create this for you if it doesn't exist.
### Summary of Required Credentials:
```bash
# In .env file
CF_API_TOKEN=your_cloudflare_api_token
CF_ZONE_ID=your_cloudflare_zone_id
CF_ACCOUNT_ID=your_cloudflare_account_id
CF_TUNNEL_ID=will_be_set_by_start_production # This will be set by start-production.sh
```
### Notes:
- The config.sh script will prompt you for these credentials during setup.
- The start-production.sh script will verify these credentials and use them to configure DNS records, create tunnels, and set up access policies.
- Ensure that the API token has the correct permissions, or the scripts will fail to configure Cloudflare services.

View File

@@ -1,215 +0,0 @@
# Coder Server Configuration
This section describes the configuration and features of the code-server environment.
## Accessing Code Server
- **URL:** `http://localhost:8080`
- **Authentication:** Password-based (see below for password retrieval)
### Retrieving the Code Server Password
After the first build, the code-server password is stored in:
```
configs/code-server/.config/code-server/config.yaml
```
Look for the `password:` field in that file. For example:
```yaml
password: 0c0dca951a2d12eff1665817
```
> **Note:** It is recommended **not** to change this password manually, as it is securely generated.
## Main Configuration Options
- `bind-addr`: The address and port code-server listens on (default: `127.0.0.1:8080`)
- `auth`: Authentication method (default: `password`)
- `password`: The login password (see above)
- `cert`: Whether to use HTTPS (default: `false`)
## Installed Tools and Features
The code-server environment includes:
- **Node.js 18+** and **npm**
- **Claude Code** (`@anthropic-ai/claude-code`) globally installed
- **Python 3** and tools:
- `python3-pip`, `python3-venv`, `python3-full`, `pipx`
- **Image and PDF processing libraries**:
- `CairoSVG`, `Pillow`, `libcairo2-dev`, `libfreetype6-dev`, `libjpeg-dev`, `libpng-dev`, `libwebp-dev`, `libtiff5-dev`, `libopenjp2-7-dev`, `liblcms2-dev`
- `weasyprint`, `fonts-roboto`
- **Git** for version control and plugin management
- **Build tools**: `build-essential`, `pkg-config`, `python3-dev`, `zlib1g-dev`
- **MkDocs Material** and a wide range of MkDocs plugins, installed in a dedicated Python virtual environment at `/home/coder/.venv/mkdocs`
- **Convenience script**: `run-mkdocs` for running MkDocs commands easily
### Using MkDocs
The virtual environment for MkDocs is automatically added to your `PATH`. You can run MkDocs commands directly, or use the provided script. For example, to build the site, from a clean terminal we would rung:
```bash
cd mkdocs
mkdocs build
```
## Claude Code Integration
<div class="github-widget" data-repo="anthropics/claude-code"></div>
The code-server environment comes with **Claude Code** (`@anthropic-ai/claude-code`) globally installed via npm.
### What is Claude Code?
Claude Code is an AI-powered coding assistant by Anthropic, designed to help you write, refactor, and understand code directly within your development environment.
### Usage
- Access Claude Code features through the command palette or sidebar in code-server.
- Use Claude Code to generate code, explain code snippets, or assist with documentation and refactoring tasks.
- For more information, refer to the [Claude Code documentation](https://docs.anthropic.com/claude/docs/claude-code).
> **Note:** Claude Code requires an API key or account with Anthropic for full functionality. Refer to the extension settings for configuration.
### Call Claude
To use claude simply type claude into the terminal and follow instructions.
```bash
claude
```
## Shell Environment
The `.bashrc` is configured to include the MkDocs virtual environment and user-local binaries in your `PATH` for convenience.
## Code Navigation and Editing Features
The code-server environment provides robust code navigation and editing features, including:
- **IntelliSense**: Smart code completions based on variable types, function definitions, and imported modules.
- **Code Navigation**: Easily navigate to definitions, references, and symbol searches within your codebase.
- **Debugging Support**: Integrated debugging support for Node.js and Python, with breakpoints, call stacks, and interactive consoles.
- **Terminal Access**: Built-in terminal access to run commands, scripts, and version control operations.
## Collaboration Features
Code-server includes features to support collaboration:
- **Live Share**: Collaborate in real-time with others, sharing your code and terminal sessions.
- **ChatGPT Integration**: AI-powered code assistance and chat-based collaboration.
## Security Considerations
When using code-server, consider the following security aspects:
- **Password Management**: The default password is securely generated. Do not share it or expose it in public repositories.
- **Network Security**: Ensure that your firewall settings allow access to the code-server port (default: 8080) only from trusted networks.
- **Data Privacy**: Be cautious when uploading sensitive data or code to the server. Use environment variables or secure vaults for sensitive information.
## Ollama Integration
<div class="github-widget" data-repo="ollama/ollama"></div>
The code-server environment includes **Ollama**, a tool for running large language models locally on your machine.
### What is Ollama?
Ollama is a lightweight, extensible framework for building and running language models locally. It provides a simple API for creating, running, and managing models, making it easy to integrate AI capabilities into your development workflow without relying on external services.
### Getting Started with Ollama
#### Staring Ollama
For ollama to be available, you need to open a terminal and run:
```bash
ollama serve
```
This will start the ollama server and you can then proceed to pulling a model and chatting.
#### Pulling a Model
To get started, you'll need to pull a model. For development and testing, we recommend starting with a smaller model like Gemma 2B:
```bash
ollama pull gemma2:2b
```
For even lighter resource usage, you can use the 1B parameter version:
```bash
ollama pull gemma2:1b
```
#### Running a Model
Once you've pulled a model, you can start an interactive session:
```bash
ollama run gemma2:2b
```
#### Available Models
Popular models available through Ollama include:
- **Gemma 2** (1B, 2B, 9B, 27B): Google's efficient language models
- **Llama 3.2** (1B, 3B, 11B, 90B): Meta's latest language models
- **Qwen 2.5** (0.5B, 1.5B, 3B, 7B, 14B, 32B, 72B): Alibaba's multilingual models
- **Phi 3.5** (3.8B): Microsoft's compact language model
- **Code Llama** (7B, 13B, 34B): Specialized for code generation
### Using Ollama in Your Development Workflow
#### API Access
Ollama provides a REST API that runs on `http://localhost:11434` by default. You can integrate this into your applications:
```bash
curl http://localhost:11434/api/generate -d '{
"model": "gemma2:2b",
"prompt": "Write a Python function to calculate fibonacci numbers",
"stream": false
}'
```
#### Model Management
List installed models:
```bash
ollama list
```
Remove a model:
```bash
ollama rm gemma2:2b
```
Show model information:
```bash
ollama show gemma2:2b
```
### Resource Considerations
- **1B models**: Require ~1GB RAM, suitable for basic tasks and resource-constrained environments
- **2B models**: Require ~2GB RAM, good balance of capability and resource usage
- **Larger models**: Provide better performance but require significantly more resources
### Integration with Development Tools
Ollama can be integrated with various development tools and editors through its API, enabling features like:
- Code completion and generation
- Documentation writing assistance
- Code review and explanation
- Automated testing suggestions
For more information, visit the [Ollama documentation](https://ollama.ai/docs).
For more detailed information on configuring and using code-server, refer to the official [code-server documentation](https://coder.com/docs/).

View File

@@ -1,6 +0,0 @@
# Configuration
There are several configuration steps to building a production ready Changemaker-Lite.
In the order we suggest doing them:

View File

@@ -1,390 +0,0 @@
# Map Configuration
The Map system is a containerized web application that visualizes geographic data from NocoDB on an interactive map using Leaflet.js. It's designed for canvassing applications and community organizing.
## Features
- 🗺️ Interactive map visualization with OpenStreetMap
- 📍 Real-time geolocation support for adding locations
- Add new locations directly from the map interface
- 🔄 Auto-refresh every 30 seconds
- 📱 Responsive design for mobile devices
- 🔒 Secure API proxy to protect NocoDB credentials
- 👤 User authentication with login system
- ⚙️ Admin panel for system configuration
- 🎯 Configurable map start location
- 📄 Walk Sheet generator for door-to-door canvassing
- 🔗 QR code integration for digital resources
- 🐳 Docker containerization for easy deployment
- 🆓 100% open source (no proprietary dependencies)
## Setup Process Overview
The setup process involves several steps that must be completed in order:
1. **Get NocoDB API Token** - Create an API token in your NocoDB instance
2. **Configure Environment** - Update the `.env` file with your NocoDB details
3. **Auto-Create Database Structure** - Run the build script to create required tables
4. **Get Table URLs** - Find and copy the URLs for the newly created tables
5. **Update Environment with URLs** - Add the table URLs to your `.env` file
6. **Build and Deploy** - Build the Docker image and start the application
## Prerequisites
- Docker and Docker Compose installed
- NocoDB instance with API access
- Domain name (optional but recommended for production)
## Step 1: Get NocoDB API Token
1. Login to your NocoDB instance
2. Click your user icon → **Account Settings**
3. Go to the **API Tokens** tab
4. Click **Create new token**
5. Set the following permissions:
- **Read**: Yes
- **Write**: Yes
- **Delete**: Yes (optional, for admin functions)
6. Copy the generated token - you'll need it for the next step
!!! warning "Token Security"
Keep your API token secure and never commit it to version control. The token provides full access to your NocoDB data.
## Step 2: Configure Environment
Edit the `.env` file in the `map/` directory:
```env
# NocoDB API Configuration
NOCODB_API_URL=https://your-nocodb-instance.com/api/v1
NOCODB_API_TOKEN=your-api-token-here
# These URLs will be populated after running build-nocodb.sh
NOCODB_VIEW_URL=
NOCODB_LOGIN_SHEET=
NOCODB_SETTINGS_SHEET=
# Server Configuration
PORT=3000
NODE_ENV=production
# Session Secret (generate with: openssl rand -hex 32)
SESSION_SECRET=your-secure-random-string
# Map Defaults (Edmonton, Alberta, Canada)
DEFAULT_LAT=53.5461
DEFAULT_LNG=-113.4938
DEFAULT_ZOOM=11
# Optional: Map Boundaries (prevents users from adding points outside area)
# BOUND_NORTH=53.7
# BOUND_SOUTH=53.4
# BOUND_EAST=-113.3
# BOUND_WEST=-113.7
# Production Settings
TRUST_PROXY=true
COOKIE_DOMAIN=.yourdomain.com
ALLOWED_ORIGINS=https://map.yourdomain.com,http://localhost:3000
```
### Required Configuration
- `NOCODB_API_URL`: Your NocoDB instance API URL (usually ends with `/api/v1`)
- `NOCODB_API_TOKEN`: The token you created in Step 1
- `SESSION_SECRET`: Generate a secure random string for session encryption
### Optional Configuration
- `DEFAULT_LAT/LNG/ZOOM`: Default map center and zoom level
- `BOUND_*`: Map boundaries to restrict where users can add points
- `COOKIE_DOMAIN`: Your domain for cookie security
- `ALLOWED_ORIGINS`: Comma-separated list of allowed origins for CORS
## Step 3: Auto-Create Database Structure
The `build-nocodb.sh` script will automatically create the required tables in your NocoDB instance.
```bash
cd map
chmod +x build-nocodb.sh
./build-nocodb.sh
```
### What the Script Creates
The script creates three tables with the following structure:
#### 1. Locations Table
Main table for storing map data:
- `Geo-Location` (Geo-Data): Format "latitude;longitude"
- `latitude` (Decimal): Precision 10, Scale 8
- `longitude` (Decimal): Precision 11, Scale 8
- `First Name` (Single Line Text): Person's first name
- `Last Name` (Single Line Text): Person's last name
- `Email` (Email): Email address
- `Phone` (Single Line Text): Phone number
- `Unit Number` (Single Line Text): Unit or apartment number
- `Address` (Single Line Text): Street address
- `Support Level` (Single Select): Options: "1", "2", "3", "4"
- 1 = Strong Support (Green)
- 2 = Moderate Support (Yellow)
- 3 = Low Support (Orange)
- 4 = No Support (Red)
- `Sign` (Checkbox): Has campaign sign
- `Sign Size` (Single Select): Options: "Regular", "Large", "Unsure"
- `Notes` (Long Text): Additional details and comments
#### 2. Login Table
User authentication table:
- `Email` (Email): User email address (Primary)
- `Name` (Single Line Text): User display name
- `Admin` (Checkbox): Admin privileges
#### 3. Settings Table
Admin configuration table:
- `key` (Single Line Text): Setting identifier
- `title` (Single Line Text): Display name
- `value` (Long Text): Setting value
- `Geo-Location` (Text): Format "latitude;longitude"
- `latitude` (Decimal): Precision 10, Scale 8
- `longitude` (Decimal): Precision 11, Scale 8
- `zoom` (Number): Map zoom level
- `category` (Single Select): Setting category
- `updated_by` (Single Line Text): Last updater email
- `updated_at` (DateTime): Last update time
- `qr_code_1_image` (Attachment): QR code 1 image
- `qr_code_2_image` (Attachment): QR code 2 image
- `qr_code_3_image` (Attachment): QR code 3 image
### Default Data
The script also creates:
- A default admin user (admin@example.com)
- A default start location setting
## Step 4: Get Table URLs
After the script completes successfully:
1. Login to your NocoDB instance
2. Navigate to your project (should be named "Map Viewer Project")
3. For each table, get the view URL:
- Click on the table name
- Copy the URL from your browser's address bar
- The URL should look like: `https://your-nocodb.com/dashboard/#/nc/project-id/table-id`
You need URLs for:
- **Locations table** → `NOCODB_VIEW_URL`
- **Login table** → `NOCODB_LOGIN_SHEET`
- **Settings table** → `NOCODB_SETTINGS_SHEET`
## Step 5: Update Environment with URLs
Edit your `.env` file and add the table URLs:
```env
# Update these with the actual URLs from your NocoDB instance
NOCODB_VIEW_URL=https://your-nocodb.com/dashboard/#/nc/project-id/locations-table-id
NOCODB_LOGIN_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/login-table-id
NOCODB_SETTINGS_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/settings-table-id
```
!!! warning "URL Format"
Make sure to use the complete dashboard URLs, not the API URLs. The application will automatically extract the project and table IDs from these URLs.
## Step 6: Build and Deploy
Build the Docker image and start the application:
```bash
# Build the Docker image
docker-compose build
# Start the application
docker-compose up -d
```
### Verify Deployment
1. Check that the container is running:
```bash
docker-compose ps
```
2. Check the logs:
```bash
docker-compose logs -f map-viewer
```
3. Access the application at `http://localhost:3000` (or your configured domain)
## Using the Map System
### User Interface
#### Main Map View
- **Interactive Map**: Click and drag to navigate
- **Add Location**: Click on the map to add a new location
- **Search**: Use the search bar to find addresses
- **Refresh**: Data refreshes automatically every 30 seconds
#### Location Markers
- **Green**: Strong Support (Level 1)
- **Yellow**: Moderate Support (Level 2)
- **Orange**: Low Support (Level 3)
- **Red**: No Support (Level 4)
#### Adding Locations
1. Click on the map where you want to add a location
2. Fill out the form with contact information
3. Select support level and sign information
4. Add any relevant notes
5. Click "Save Location"
### Authentication
#### User Login
- Users must be added to the Login table in NocoDB
- Login with email address (no password required for simplified setup)
- Admin users have additional privileges
#### Admin Access
- Admin users can access `/admin.html`
- Configure map start location
- Set up walk sheet generator
- Manage QR codes and settings
### Admin Panel Features
#### Start Location Configuration
- **Interactive Map**: Visual interface for selecting coordinates
- **Real-time Preview**: See changes immediately
- **Validation**: Built-in coordinate and zoom level validation
#### Walk Sheet Generator
- **Printable Forms**: Generate 8.5x11 walk sheets for door-to-door canvassing
- **QR Code Integration**: Add up to 3 QR codes with custom URLs and labels
- **Form Field Matching**: Automatically matches fields from the main location form
- **Live Preview**: See changes as you type
- **Print Optimization**: Proper formatting for printing or PDF export
## API Endpoints
### Public Endpoints
- `GET /api/locations` - Fetch all locations (requires auth)
- `POST /api/locations` - Create new location (requires auth)
- `GET /api/locations/:id` - Get single location (requires auth)
- `PUT /api/locations/:id` - Update location (requires auth)
- `DELETE /api/locations/:id` - Delete location (requires auth)
- `GET /api/config/start-location` - Get map start location
- `GET /health` - Health check
### Authentication Endpoints
- `POST /api/auth/login` - User login
- `GET /api/auth/check` - Check authentication status
- `POST /api/auth/logout` - User logout
### Admin Endpoints (requires admin privileges)
- `GET /api/admin/start-location` - Get start location with source info
- `POST /api/admin/start-location` - Update map start location
- `GET /api/admin/walk-sheet-config` - Get walk sheet configuration
- `POST /api/admin/walk-sheet-config` - Save walk sheet configuration
## Troubleshooting
### Common Issues
#### Locations not showing
- Verify table has required columns (`Geo-Location`, `latitude`, `longitude`)
- Check that coordinates are valid numbers
- Ensure API token has read permissions
- Verify `NOCODB_VIEW_URL` is correct
#### Cannot add locations
- Verify API token has write permissions
- Check browser console for errors
- Ensure coordinates are within valid ranges
- Verify user is authenticated
#### Authentication issues
- Verify login table is properly configured
- Check that user email exists in Login table
- Ensure `NOCODB_LOGIN_SHEET` URL is correct
#### Build script failures
- Check that `NOCODB_API_URL` and `NOCODB_API_TOKEN` are correct
- Verify NocoDB instance is accessible
- Check network connectivity
- Review script output for specific error messages
### Development Mode
For development and debugging:
```bash
cd map/app
npm install
npm run dev
```
This will start the application with hot reload and detailed logging.
### Logs and Monitoring
View application logs:
```bash
docker-compose logs -f map-viewer
```
Check health status:
```bash
curl http://localhost:3000/health
```
## Security Considerations
1. **API Token Security**: Keep tokens secure and rotate regularly
2. **HTTPS**: Use HTTPS in production
3. **CORS Configuration**: Set appropriate `ALLOWED_ORIGINS`
4. **Cookie Security**: Configure `COOKIE_DOMAIN` properly
5. **Input Validation**: All inputs are validated server-side
6. **Rate Limiting**: API endpoints have rate limiting
7. **Session Security**: Use a strong `SESSION_SECRET`
## Maintenance
### Regular Updates
```bash
# Stop the application
docker-compose down
# Pull updates (if using git)
git pull origin main
# Rebuild and restart
docker-compose build
docker-compose up -d
```
### Backup Considerations
- NocoDB data is stored in your NocoDB instance
- Back up your `.env` file securely
- Consider backing up QR code images from the Settings table
### Performance Tips
- Monitor NocoDB performance and scaling
- Consider enabling caching for high-traffic deployments
- Use CDN for static assets if needed
- Monitor Docker container resource usage
## Support
For issues or questions:
1. Check the troubleshooting section above
2. Review NocoDB documentation
3. Check Docker and Docker Compose documentation
4. Open an issue on GitHub

View File

@@ -1,143 +0,0 @@
# MkDocs Customization & Features Overview
BNKops has been building our own features, widgets, and css styles for MKdocs material theme.
This document explains the custom styling, repository widgets, and key features enabled in this MkDocs site.
For more info on how to build your site see [Site Build](../build/site.md)
---
## Using the Repository Widget in Documentation
You can embed repository widgets directly in your Markdown documentation to display live repository stats and metadata.
To do this, add a `div` with the appropriate class and `data-repo` attribute for the repository you want to display.
**Example (for a Gitea repository):**
```html
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
```
This will render a styled card with information about the `admin/changemaker.lite` repository:
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
**Options:**
You can control the widget display with additional data attributes:
- `data-show-description="false"` — Hide the description
- `data-show-language="false"` — Hide the language
- `data-show-last-update="false"` — Hide the last update date
**Example with options:**
```html
<div class="gitea-widget" data-repo="admin/changemaker.lite" data-show-description="false"></div>
```
For GitHub repositories, use the `github-widget` class:
```html
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
```
---
## Custom CSS Styling (`stylesheets/extra.css`)
The `extra.css` file provides extensive custom styling for the site, including:
- **Login and Git Code Buttons**:
Custom styles for `.login-button` and `.git-code-button` to create visually distinct, modern buttons with hover effects.
- **Code Block Improvements**:
Forces code blocks to wrap text (`white-space: pre-wrap`) and ensures inline code and tables with code display correctly on all devices.
- **GitHub Widget Styles**:
Styles for `.github-widget` and its subcomponents, including:
- Card-like container with gradient backgrounds and subtle box-shadows.
- Header with icon, repo link, and stats (stars, forks, issues).
- Description area with accent border.
- Footer with language, last update, and license info.
- Loading and error states with spinners and error messages.
- Responsive grid layout for multiple widgets.
- Compact variant for smaller displays.
- Dark mode adjustments.
- **Gitea Widget Styles**:
Similar to GitHub widget, but with Gitea branding (green accents).
Includes `.gitea-widget`, `.gitea-widget-container`, and related classes for header, stats, description, footer, loading, and error states.
- **Responsive Design**:
Media queries ensure widgets and tables look good on mobile devices.
---
## Repository Widgets
### Data Generation (`hooks/repo_widget_hook.py`)
- **Purpose**:
During the MkDocs build, this hook fetches metadata for a list of GitHub and Gitea repositories and writes JSON files to `docs/assets/repo-data/`.
- **How it works**:
- Runs before build (unless in `serve` mode).
- Fetches repo data (stars, forks, issues, language, etc.) via GitHub/Gitea APIs.
- Outputs a JSON file per repo (e.g., `lyqht-mini-qr.json`).
- Used by frontend widgets for fast, client-side rendering.
### GitHub Widget (`javascripts/github-widget.js`)
- **Purpose**:
Renders a card for each GitHub repository using the pre-generated JSON data.
- **Features**:
- Displays repo name, link, stars, forks, open issues, language, last update, and license.
- Shows loading spinner while fetching data.
- Handles errors gracefully.
- Supports dynamic content (re-initializes on DOM changes).
- Language color coding for popular languages.
### Gitea Widget (`javascripts/gitea-widget.js`)
- **Purpose**:
Renders a card for each Gitea repository using the pre-generated JSON data.
- **Features**:
- Similar to GitHub widget, but styled for Gitea.
- Shows repo name, link, stars, forks, open issues, language, last update.
- Loading and error states.
- Language color coding.
---
## MkDocs Features (`mkdocs.yml`)
Key features and plugins enabled:
- **Material Theme**:
Modern, responsive UI with dark/light mode toggle, custom fonts, and accent colors.
- **Navigation Enhancements**:
- Tabs, sticky navigation, instant loading, breadcrumbs, and sectioned navigation.
- Table of contents with permalinks.
- **Content Features**:
- Code annotation, copy buttons, tooltips, and improved code highlighting.
- Admonitions, tabbed content, task lists, and emoji support.
- **Plugins**:
- **Search**: Advanced search with custom tokenization.
- **Social**: OpenGraph/social card generation.
- **Blog**: Blogging support with archives and categories.
- **Tags**: Tagging for content organization.
- **Custom Hooks**:
- `repo_widget_hook.py` for repository widget data.
- **Extra CSS/JS**:
- Custom styles and scripts for widgets and homepage.
- **Extra Configuration**:
- Social links, copyright.
---
## Summary
This MkDocs site is highly customized for developer documentation, with visually rich repository widgets, improved code and table rendering, and a modern, responsive UI.
All repository stats are fetched at build time for performance and reliability.

View File

@@ -1,129 +0,0 @@
# V1 Documentation (Deprecated)
!!! warning "V1 is Legacy"
Changemaker Lite V1 is deprecated and no longer actively maintained. These docs are preserved for reference only.
## Migrating to V2
Changemaker Lite V2 is a complete architectural rebuild with significant improvements:
A quick test because why not.
### Why Upgrade to V2?
**Modern Stack**
- TypeScript throughout (V1 was JavaScript)
- Prisma ORM (V1 used NocoDB REST API)
- JWT auth (V1 used session cookies)
- React admin (V1 used server-rendered HTML)
**Better Performance**
- Direct database access (no NocoDB middleware)
- Redis-backed caching and rate limiting
- BullMQ job queues for async operations
- Optimized queries with Prisma
**Enhanced Security**
- Security audit completed (Feb 2026)
- Password policy enforcement (12+ chars, complexity)
- Refresh token rotation in transactions
- XSS/injection prevention throughout
- Rate limiting on all sensitive endpoints
**New Features**
- Volunteer canvassing system with GPS tracking
- Landing page builder (GrapesJS)
- Email template management
- Media library (video management)
- Observability dashboard (Prometheus + Grafana)
- NAR 2025 data import (Canadian electoral data)
[View complete V2 documentation →](../v2/index.md){ .md-button .md-button--primary }
## Migration Guide
Ready to migrate? Follow our step-by-step guide:
[→ V1 to V2 Migration Guide](../v2/migration/index.md){ .md-button }
The migration guide covers:
1. **Breaking Changes** - NocoDB → Prisma, API endpoint changes
2. **Data Migration** - Export V1 data, transform, import to V2
3. **Configuration Changes** - Environment variables, service names
4. **Feature Parity** - V1 vs V2 feature comparison
## V1 Documentation Archive
These docs are preserved for existing V1 installations:
### Build Guides
- [Build Overview](build/index.md)
- [Build Server](build/server.md)
- [Build Map](build/map.md)
- [Build Influence](build/influence.md)
- [Build Site](build/site.md)
### Services
- [Services Overview](services/index.md)
- [Homepage](services/homepage.md)
- [Code Server](services/code-server.md)
- [MKDocs](services/mkdocs.md)
- [Listmonk](services/listmonk.md)
- [PostgreSQL](services/postgresql.md)
- [n8n](services/n8n.md)
- [NocoDB](services/nocodb.md)
- [Gitea](services/gitea.md)
- [Map](services/map.md)
- [Mini QR](services/mini-qr.md)
### Configuration
- [Config Overview](config/index.md)
- [Cloudflare](config/cloudflare-config.md)
- [MKdocs](config/mkdocs.md)
- [Code Server](config/coder.md)
- [Map](config/map.md)
### Manuals
- [Manual Overview](manual/index.md)
- [Map Manual](manual/map.md)
### Advanced
- [Advanced Overview](adv/index.md)
- [SSH + Tailscale + Ansible](adv/ansible.md)
- [SSH + VScode](adv/vscode-ssh.md)
## V1 Architecture (Reference)
V1 used a two-app architecture with NocoDB as the data layer:
**Influence App** (port 3333)
- Express.js server with server-rendered HTML
- NocoDB REST API for database operations
- Session-based authentication (Redis)
- Bull job queues for emails
**Map App** (port 3000)
- Express.js server with Leaflet.js maps
- NocoDB REST API for database operations
- QR code generation
- Volunteer shift management
**Shared Infrastructure**
- NocoDB (data layer)
- Redis (sessions, cache, queues)
- PostgreSQL (via NocoDB)
- Cloudflare tunnels
## Support for V1
V1 is no longer under active development. We recommend migrating to V2.
For critical V1 issues:
- Check existing V1 documentation
- Review V1 code in `/influence` and `/map` directories
- Consider migrating to V2
---
**Ready to upgrade?** [Start with the V2 Quick Start Guide →](../v2/getting-started/quick-start.md)

View File

@@ -1,3 +0,0 @@
# Manuals
The following are manuals, some accompanied by videos, on the use of the system.

View File

@@ -1,407 +0,0 @@
# Map System Manual
This comprehensive manual covers all features of the Map System - a powerful campaign management platform with interactive mapping, volunteer coordination, data management, and communication tools. *(Insert screenshot - feature overview)*
---
## 1. Getting Started
### Logging In
1. Go to your map site URL (e.g., `https://yoursite.com` or `http://localhost:3000`).
2. Enter your email and password on the login page.
3. Click **Login**.
4. If you forget your password, use the **Reset Password** link or contact an admin.
5. **Password Recovery**: Check your email for reset instructions if SMTP is configured. *(Insert screenshot - login page)*
### User Types & Permissions
- **Admin**: Full access to all features, user management, and system configuration
- **User**: Access to map, shifts, profile management, and location data
- **Temp**: Limited access (add/edit locations only, expires automatically after shift date)
---
## 2. Interactive Map Features
### Basic Map Navigation
1. After login, you'll see the interactive map with location markers.
2. Use mouse or touch to pan and zoom around the map.
3. Your current location may appear as a blue dot (if location services enabled).
4. Use the zoom controls (+/-) or mouse wheel to adjust map scale. *(Insert screenshot - main map view)*
### Advanced Search (Ctrl+K)
1. Press **Ctrl+K** anywhere on the site to open the universal search.
2. Search for:
- **Addresses**: Find and navigate to specific locations
- **Documentation**: Search help articles and guides
- **Locations**: Find existing data points by name or details
3. Click results to navigate directly to locations on the map.
4. **QR Code Generation**: Search results include QR codes for easy mobile sharing. *(Insert screenshot - search interface)*
### Map Overlays (Cuts)
1. **Public Cuts**: Geographic overlays (wards, neighborhoods, districts) are automatically displayed.
2. **Cut Selector**: Use the multi-select dropdown to show/hide different cuts.
3. **Mobile Interface**: On mobile, tap the 🗺️ button to manage overlays.
4. **Legend**: View active cuts with color coding and labels.
5. Cuts help organize and filter location data by geographic regions. *(Insert screenshot - cuts interface)*
---
## 3. Location Management
### Adding New Locations
1. Click the **Add Location** button (+ icon) on the map.
2. Click on the map where you want to place the new location.
3. Fill out the comprehensive form:
- **Personal**: First Name, Last Name, Email, Phone, Unit Number
- **Political**: Support Level (1-4 scale), Party Affiliation
- **Address**: Street Address (auto-geocoded when possible)
- **Campaign**: Lawn Sign (Yes/No/Maybe), Sign Size, Volunteer Interest
- **Notes**: Additional information and comments
4. **Address Confirmation**: System validates and confirms addresses when possible.
5. Click **Save** to add the location marker. *(Insert screenshot - add location form)*
### Editing and Managing Locations
1. Click on any location marker to view details.
2. **Popup Actions**:
- **Edit**: Modify all location details
- **Move**: Drag marker to new position (admin/user only)
- **Delete**: Remove location (admin/user only - hidden for temp users)
3. **Quick Actions**: Email, phone, or text contact directly from popup.
4. **Support Level Color Coding**: Markers change color based on support level.
5. **Apartment View**: Special clustering for apartment buildings. *(Insert screenshot - location popup)*
### Bulk Data Import
1. **Admin Panel****Data Converter****Upload CSV**
2. **Supported Formats**: CSV files with address data
3. **Batch Geocoding**: Automatically converts addresses to coordinates
4. **Progress Tracking**: Visual progress bar with success/failure reporting
5. **Error Handling**: Downloadable error reports for failed geocoding
6. **Validation**: Preview and verify data before final import
7. **Edmonton Data**: Pre-configured for City of Edmonton neighborhood data. *(Insert screenshot - data import interface)*
---
## 4. Volunteer Shift Management
### Public Shift Signup (No Login Required)
1. Visit the **Public Shifts** page (accessible without account).
2. Browse available volunteer opportunities with:
- Date, time, and location information
- Available spots and current signups
- Detailed shift descriptions
3. **One-Click Signup**:
- Enter name, email, and phone number
- Automatic temporary account creation
- Instant email confirmation with login details
4. **Account Expiration**: Temp accounts automatically expire after shift date. *(Insert screenshot - public shifts page)*
### Authenticated User Shift Management
1. Go to **Shifts** from the main navigation.
2. **View Options**:
- **Grid View**: List format with detailed information
- **Calendar View**: Monthly calendar with shift visualization
3. **Filter Options**: Date range, shift type, and availability status.
4. **My Signups**: View your confirmed shifts at the top of the page.
### Shift Actions
- **Sign Up**: Join available shifts (if spots remain)
- **Cancel**: Remove yourself from shifts you've joined
- **Calendar Export**: Add shifts to Google Calendar, Outlook, or Apple Calendar
- **Shift Details**: View full descriptions, requirements, and coordinator info. *(Insert screenshot - shifts interface)*
---
## 5. Advanced Map Features
### Geographic Cuts System
**What are Cuts?**: Polygon overlays that define geographic regions like wards, neighborhoods, or custom areas.
#### Viewing Cuts (All Users)
1. **Auto-Display**: Public cuts appear automatically when map loads.
2. **Multi-Select Control**: Desktop users see dropdown with checkboxes for each cut.
3. **Mobile Modal**: Touch the 🗺️ button for full-screen cut management.
4. **Quick Actions**: "Show All" / "Hide All" buttons for easy control.
5. **Color Coding**: Each cut has unique colors and opacity settings. *(Insert screenshot - cuts display)*
#### Admin Cut Management
1. **Admin Panel****Map Cuts** for full management interface.
2. **Drawing Tools**: Click-to-add-points polygon creation system.
3. **Cut Properties**:
- Name, description, and category
- Color and opacity customization
- Public visibility settings
- Official designation markers
4. **Cut Operations**:
- Create, edit, duplicate, and delete cuts
- Import/export cut data as JSON
- Location filtering within cut boundaries
5. **Statistics Dashboard**: Analyze location data within cut boundaries.
6. **Print Functionality**: Generate professional reports with maps and data tables. *(Insert screenshot - cut management)*
### Location Filtering within Cuts
1. **View Cut**: Select a cut from the admin interface.
2. **Filter Locations**: Automatically shows only locations within cut boundaries.
3. **Statistics Panel**: Real-time counts of:
- Total locations within cut
- Support level breakdown (Strong/Lean/Undecided/Opposition)
- Contact information availability (email/phone)
- Lawn sign placements
4. **Export Options**: Download filtered location data as CSV.
5. **Print Reports**: Generate professional cut reports with statistics and location tables. *(Insert screenshot - cut filtering)*
---
## 6. Communication Tools
### Universal Search & Contact
1. **Ctrl+K Search**: Find and contact anyone in your database instantly.
2. **Direct Contact Links**: Email and phone links throughout the interface.
3. **QR Code Generation**: Share contact information via QR codes.
### Admin Communication Features
1. **Bulk Email System**:
- Rich HTML email composer with formatting toolbar
- Live email preview before sending
- Broadcast to all users with progress tracking
- Individual delivery status for each recipient
2. **One-Click Communication Buttons**:
- **📧 Email**: Launch email client with pre-filled recipient
- **📞 Call**: Open phone dialer with contact's number
- **💬 SMS**: Launch text messaging with contact's number
3. **Shift Communication**:
- Email shift details to all volunteers
- Individual volunteer contact from shift management
- Automated signup confirmations and reminders. *(Insert screenshot - communication tools)*
---
## 7. Walk Sheet Generator
### Creating Walk Sheets
1. **Admin Panel****Walk Sheet Generator**
2. **Configuration Options**:
- Title, subtitle, and footer text
- Contact information and instructions
- QR codes for digital resources
- Logo and branding elements
3. **Location Selection**: Choose specific areas or use cut boundaries.
4. **Print Options**: Multiple layout formats for different campaign needs.
5. **QR Integration**: Add QR codes linking to:
- Digital surveys or forms
- Contact information
- Campaign websites or resources. *(Insert screenshot - walk sheet generator)*
### Mobile-Optimized Walk Sheets
1. **Responsive Design**: Optimized for viewing on phones and tablets.
2. **QR Code Scanner Integration**: Quick scanning for volunteer check-ins.
3. **Offline Capability**: Download for use without internet connection.
---
## 8. User Profile Management
### Personal Settings
1. **User Menu****Profile** to access personal settings.
2. **Account Information**:
- Update name, email, and phone number
- Change password
- Communication preferences
3. **Activity History**: View your shift signups and location contributions.
4. **Privacy Settings**: Control data sharing and communication preferences. *(Insert screenshot - user profile)*
### Password Recovery
1. **Forgot Password** link on login page.
2. **Email Reset**: Automated password reset via SMTP (if configured).
3. **Admin Assistance**: Contact administrators for manual password resets.
---
## 9. Admin Panel Features
### Dashboard Overview
1. **System Statistics**: User counts, recent activity, and system health.
2. **Quick Actions**: Direct access to common administrative tasks.
3. **NocoDB Integration**: Direct links to database management interface. *(Insert screenshot - admin dashboard)*
### User Management
1. **Create Users**: Add new accounts with role assignments:
- **Regular Users**: Full access to mapping and shifts
- **Temporary Users**: Limited access with automatic expiration
- **Admin Users**: Full system administration privileges
2. **User Communication**:
- Send login details to new users
- Bulk email all users with rich HTML composer
- Individual user contact (email, call, text)
3. **User Types & Expiration**:
- Set expiration dates for temporary accounts
- Visual indicators for user types and status
- Automatic cleanup of expired accounts. *(Insert screenshot - user management)*
### Shift Administration
1. **Create & Manage Shifts**:
- Set dates, times, locations, and volunteer limits
- Public/private visibility settings
- Detailed descriptions and requirements
2. **Volunteer Management**:
- Add users directly to shifts
- Remove volunteers when needed
- Email shift details to all participants
- Generate public signup links
3. **Volunteer Communication**:
- Individual contact buttons (email, call, text) for each volunteer
- Bulk shift detail emails with delivery tracking
- Automated confirmation and reminder systems. *(Insert screenshot - shift management)*
### System Configuration
1. **Map Settings**:
- Set default start location and zoom level
- Configure map boundaries and restrictions
- Customize marker styles and colors
2. **Integration Management**:
- NocoDB database connections
- Listmonk email list synchronization
- SMTP configuration for automated emails
3. **Security Settings**:
- User permissions and role management
- API access controls
- Session management. *(Insert screenshot - system config)*
---
## 10. Data Management & Integration
### NocoDB Database Integration
1. **Direct Database Access**: Admin links to NocoDB sheets for advanced data management.
2. **Automated Sync**: Real-time synchronization between map interface and database.
3. **Backup & Migration**: Built-in tools for data backup and system migration.
4. **Custom Fields**: Add custom data fields through NocoDB interface.
### Listmonk Email Marketing Integration
1. **Automatic List Sync**: Map data automatically syncs to Listmonk email lists.
2. **Segmentation**: Create targeted lists based on:
- Geographic location (cuts/neighborhoods)
- Support levels and volunteer interest
- Contact preferences and activity
3. **One-Direction Sync**: Maintains data integrity while allowing email unsubscribes.
4. **Compliance**: Newsletter legislation compliance with opt-out capabilities. *(Insert screenshot - integration settings)*
### Data Export & Reporting
1. **CSV Export**: Download location data, user lists, and shift reports.
2. **Cut Reports**: Professional reports with statistics and location breakdowns.
3. **Print-Ready Formats**: Optimized layouts for physical distribution.
4. **Analytics Dashboard**: Track user engagement and system usage.
---
## 11. Mobile & Accessibility Features
### Mobile-Optimized Interface
1. **Responsive Design**: Fully functional on phones and tablets.
2. **Touch Navigation**: Optimized touch controls for map interaction.
3. **Mobile-Specific Features**:
- Cut management modal for overlay control
- Simplified navigation and larger touch targets
- Offline capability for basic functions
### Accessibility
1. **Keyboard Navigation**: Full keyboard support throughout the interface.
2. **Screen Reader Compatibility**: ARIA labels and semantic markup.
3. **High Contrast Support**: Compatible with accessibility themes.
4. **Text Scaling**: Responsive to browser zoom and text size settings.
---
## 12. Security & Privacy
### Data Protection
1. **Server-Side Security**: All API tokens and credentials kept server-side only.
2. **Input Validation**: Comprehensive validation and sanitization of all user inputs.
3. **CORS Protection**: Cross-origin request security measures.
4. **Rate Limiting**: Protection against abuse and automated attacks.
### User Privacy
1. **Role-Based Access**: Users only see data appropriate to their permission level.
2. **Temporary Account Expiration**: Automatic cleanup of temporary user data.
3. **Audit Trails**: Logging of administrative actions and data changes.
4. **Data Retention**: Configurable retention policies for different data types. *(Insert screenshot - security settings)*
### Authentication
1. **Secure Login**: Password-based authentication with optional 2FA.
2. **Session Management**: Automatic logout for expired sessions.
3. **Password Policies**: Configurable password strength requirements.
4. **Account Lockout**: Protection against brute force attacks.
---
## 13. Performance & System Requirements
### System Performance
1. **Optimized Database Queries**: Reduced API calls by over 5000% for better performance.
2. **Smart Caching**: Intelligent caching of frequently accessed data.
3. **Progressive Loading**: Map data loads incrementally for faster initial page loads.
4. **Background Sync**: Automatic data synchronization without blocking user interface.
### Browser Requirements
1. **Modern Browsers**: Chrome, Firefox, Safari, Edge (recent versions).
2. **JavaScript Required**: Full functionality requires JavaScript enabled.
3. **Local Storage**: Uses browser storage for session management and caching.
4. **Geolocation**: Optional location services for enhanced functionality.
---
## 14. Troubleshooting
### Common Issues
- **Locations not showing**: Check database connectivity, verify coordinates are valid, ensure API permissions allow read access.
- **Cannot add locations**: Verify API write permissions, check coordinate bounds, ensure all required fields completed.
- **Login problems**: Verify email/password, check account expiration (for temp users), contact admin for password reset.
- **Map not loading**: Check internet connection, verify site URL, clear browser cache and cookies.
- **Permission denied**: Confirm user role and permissions, check account expiration status, contact administrator.
### Performance Issues
- **Slow loading**: Check internet connection, try refreshing the page, contact admin if problems persist.
- **Database errors**: Contact system administrator, check NocoDB service status.
- **Email not working**: Verify SMTP configuration (admin), check spam/junk folders.
### Mobile Issues
- **Touch problems**: Ensure touch targets are accessible, try refreshing page, check for browser compatibility.
- **Display issues**: Try rotating device, check browser zoom level, update to latest browser version.
---
## 15. Advanced Features
### API Access
1. **RESTful API**: Programmatic access to map data and functionality.
2. **Authentication**: Token-based API authentication for external integrations.
3. **Rate Limiting**: API usage limits to ensure system stability.
4. **Documentation**: Complete API documentation for developers.
### Customization Options
1. **Theming**: Customizable color schemes and branding.
2. **Field Configuration**: Add custom data fields through admin interface.
3. **Workflow Customization**: Configurable user workflows and permissions.
4. **Integration Hooks**: Webhook support for external system integration.
---
## 16. Getting Help & Support
### Built-in Help
1. **Context Help**: Tooltips and help text throughout the interface.
2. **Search Documentation**: Use Ctrl+K to search help articles and guides.
3. **Status Messages**: Clear feedback for all user actions and system status.
### Administrator Support
1. **Contact Admin**: Use the contact information provided during setup.
2. **System Logs**: Administrators have access to detailed system logs for troubleshooting.
3. **Database Direct Access**: Admins can access NocoDB directly for advanced data management.
### Community Resources
1. **Documentation**: Comprehensive online documentation and guides.
2. **GitHub Repository**: Access to source code and issue tracking.
3. **Developer Community**: Active community for advanced customization and development.
For technical support, contact your system administrator or refer to the comprehensive documentation available through the help system. *(Insert screenshot - help resources)*

View File

@@ -1,61 +0,0 @@
# Code Server
![code](code.png)
<div class="github-widget" data-repo="coder/code-server"></div>
## Overview
Code Server provides a full Visual Studio Code experience in your web browser, allowing you to develop from any device. It runs on your server and provides access to your development environment through a web interface.
## Features
- Full VS Code experience in the browser
- Extensions support
- Terminal access
- Git integration
- File editing and management
- Multi-language support
## Access
- **Default Port**: 8888
- **URL**: `http://localhost:8888`
- **Default Workspace**: `/home/coder/mkdocs/`
## Configuration
### Environment Variables
- `DOCKER_USER`: The user to run code-server as (default: `coder`)
- `DEFAULT_WORKSPACE`: Default workspace directory
- `USER_ID`: User ID for file permissions
- `GROUP_ID`: Group ID for file permissions
### Volumes
- `./configs/code-server/.config`: VS Code configuration
- `./configs/code-server/.local`: Local data
- `./mkdocs`: Main workspace directory
## Usage
1. Access Code Server at `http://localhost:8888`
2. Open the `/home/coder/mkdocs/` workspace
3. Start editing your documentation files
4. Install extensions as needed
5. Use the integrated terminal for commands
## Useful Extensions
Consider installing these extensions for better documentation work:
- Markdown All in One
- Material Design Icons
- GitLens
- Docker
- YAML
## Official Documentation
For more detailed information, visit the [official Code Server documentation](https://coder.com/docs/code-server).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

View File

@@ -1,57 +0,0 @@
# Gitea
![git](git.png)
<div class="github-widget" data-repo="go-gitea/gitea"></div>
Self-hosted Git service for collaborative development.
## Overview
Gitea is a lightweight, self-hosted Git service similar to GitHub, GitLab, and Bitbucket. It provides a web interface for managing repositories, issues, pull requests, and more.
## Features
- Git repository hosting
- Web-based interface
- Issue tracking
- Pull requests
- Wiki and code review
- Lightweight and easy to deploy
## Access
- **Default Web Port**: `${GITEA_WEB_PORT:-3030}` (default: 3030)
- **Default SSH Port**: `${GITEA_SSH_PORT:-2222}` (default: 2222)
- **URL**: `http://localhost:${GITEA_WEB_PORT:-3030}`
- **Default Data Directory**: `/data/gitea`
## Configuration
### Environment Variables
- `GITEA__database__DB_TYPE`: Database type (e.g., `sqlite3`, `mysql`, `postgres`)
- `GITEA__database__HOST`: Database host (default: `${GITEA_DB_HOST:-gitea-db:3306}`)
- `GITEA__database__NAME`: Database name (default: `${GITEA_DB_NAME:-gitea}`)
- `GITEA__database__USER`: Database user (default: `${GITEA_DB_USER:-gitea}`)
- `GITEA__database__PASSWD`: Database password (from `.env`)
- `GITEA__server__ROOT_URL`: Root URL (e.g., `${GITEA_ROOT_URL}`)
- `GITEA__server__HTTP_PORT`: Web port (default: 3000 inside container)
- `GITEA__server__DOMAIN`: Domain (e.g., `${GITEA_DOMAIN}`)
### Volumes
- `gitea_data:/data`: Gitea configuration and data
- `/etc/timezone:/etc/timezone:ro`
- `/etc/localtime:/etc/localtime:ro`
## Usage
1. Access Gitea at `http://localhost:${GITEA_WEB_PORT:-3030}`
2. Register or log in as an admin user
3. Create or import repositories
4. Collaborate with your team
## Official Documentation
For more details, visit the [official Gitea documentation](https://docs.gitea.com/).

View File

@@ -1,211 +0,0 @@
# Homepage
![dashboard](dashboard.png)
<div class="github-widget" data-repo="gethomepage/homepage"></div>
Modern dashboard for accessing all your self-hosted services.
## Overview
Homepage is a modern, fully static, fast, secure fully configurable application dashboard with integrations for over 100 services. It provides a beautiful and customizable interface to access all your Changemaker Lite services from a single location.
## Features
- **Service Dashboard**: Central hub for all your applications
- **Docker Integration**: Automatic service discovery and monitoring
- **Customizable Layout**: Flexible grid-based layout system
- **Service Widgets**: Live status and metrics for services
- **Quick Search**: Fast navigation with built-in search
- **Bookmarks**: Organize frequently used links
- **Dark/Light Themes**: Multiple color schemes available
- **Responsive Design**: Works on desktop and mobile devices
## Access
- **Default Port**: 3010
- **URL**: `http://localhost:3010`
- **Configuration**: YAML-based configuration files
## Configuration
### Environment Variables
- `HOMEPAGE_PORT`: External port mapping (default: 3010)
- `PUID`: User ID for file permissions (default: 1000)
- `PGID`: Group ID for file permissions (default: 1000)
- `TZ`: Timezone setting (default: Etc/UTC)
- `HOMEPAGE_ALLOWED_HOSTS`: Allowed hosts for the dashboard
### Configuration Files
Homepage uses YAML configuration files located in `./configs/homepage/`:
- `settings.yaml`: Global settings and theme configuration
- `services.yaml`: Service definitions and widgets
- `bookmarks.yaml`: Bookmark categories and links
- `widgets.yaml`: Dashboard widgets configuration
- `docker.yaml`: Docker integration settings
### Volumes
- `./configs/homepage:/app/config`: Configuration files
- `./assets/icons:/app/public/icons`: Custom service icons
- `./assets/images:/app/public/images`: Background images and assets
- `/var/run/docker.sock:/var/run/docker.sock`: Docker socket for container monitoring
## Changemaker Lite Services
Homepage is pre-configured with all Changemaker Lite services:
### Essential Tools
- **Code Server** (Port 8888): VS Code in the browser
- **Listmonk** (Port 9000): Newsletter & mailing list manager
- **NocoDB** (Port 8090): No-code database platform
### Content & Documentation
- **MkDocs** (Port 4000): Live documentation server
- **Static Site** (Port 4001): Built documentation hosting
### Automation & Data
- **n8n** (Port 5678): Workflow automation platform
- **PostgreSQL** (Port 5432): Database backends
## Customization
### Adding Custom Services
Edit `configs/homepage/services.yaml` to add new services:
```yaml
- Custom Category:
- My Service:
href: http://localhost:8080
description: Custom service description
icon: mdi-application
widget:
type: ping
url: http://localhost:8080
```
### Custom Icons
Add custom icons to `./assets/icons/` directory and reference them in services.yaml:
```yaml
icon: /icons/my-custom-icon.png
```
### Themes and Styling
Modify `configs/homepage/settings.yaml` to customize appearance:
```yaml
theme: dark # or light
color: purple # slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
```
### Widgets
Enable live monitoring widgets in `configs/homepage/services.yaml`:
```yaml
- Service Name:
widget:
type: docker
container: container-name
server: my-docker
```
## Service Monitoring
Homepage can display real-time status information for your services:
- **Docker Integration**: Container status and resource usage
- **HTTP Ping**: Service availability monitoring
- **Custom APIs**: Integration with service-specific APIs
## Docker Integration
Homepage monitors Docker containers automatically when configured:
1. Ensure Docker socket is mounted (`/var/run/docker.sock`)
2. Configure container mappings in `docker.yaml`
3. Add widget configurations to `services.yaml`
## Security Considerations
- Homepage runs with limited privileges
- Configuration files should have appropriate permissions
- Consider network isolation for production deployments
- Use HTTPS for external access
- Regularly update the Homepage image
## Troubleshooting
### Common Issues
**Configuration not loading**: Check YAML syntax in configuration files
```bash
docker logs homepage-changemaker
```
**Icons not displaying**: Verify icon paths and file permissions
```bash
ls -la ./assets/icons/
```
**Services not reachable**: Verify network connectivity between containers
```bash
docker exec homepage-changemaker ping service-name
```
**Widget data not updating**: Check Docker socket permissions and container access
```bash
docker exec homepage-changemaker ls -la /var/run/docker.sock
```
## Configuration Examples
### Basic Service Widget
```yaml
- Code Server:
href: http://localhost:8888
description: VS Code in the browser
icon: code-server
widget:
type: docker
container: code-server-changemaker
```
### Custom Dashboard Layout
```yaml
# settings.yaml
layout:
style: columns
columns: 3
# Responsive breakpoints
responsive:
mobile: 1
tablet: 2
desktop: 3
```
## Official Documentation
For comprehensive configuration guides and advanced features:
- [Homepage Documentation](https://gethomepage.dev/)
- [GitHub Repository](https://github.com/gethomepage/homepage)
- [Configuration Examples](https://gethomepage.dev/configs/)
- [Widget Integrations](https://gethomepage.dev/widgets/)

View File

@@ -1,118 +0,0 @@
# Services
Changemaker Lite includes several powerful services that work together to provide a complete documentation and development platform. Each service is containerized and can be accessed through its dedicated port.
## Available Services
### [Code Server](code-server.md)
**Port: 8888** | Visual Studio Code in your browser for remote development
<div class="github-widget" data-repo="coder/code-server"></div>
- Full IDE experience
- Extensions support
- Git integration
- Terminal access
### [Listmonk](listmonk.md)
**Port: 9000** | Self-hosted newsletter and mailing list manager
<div class="github-widget" data-repo="knadh/listmonk"></div>
- Email campaigns
- Subscriber management
- Analytics
- Template system
### [PostgreSQL](postgresql.md)
**Port: 5432** | Reliable database backend
- Data persistence for Listmonk
- ACID compliance
- High performance
- Backup and restore capabilities
### [MkDocs Material](mkdocs.md)
**Port: 4000** | Documentation site generator with live preview
<div class="github-widget" data-repo="squidfunk/mkdocs-material"></div>
- Material Design theme
- Live reload
- Search functionality
- Markdown support
### [Static Site Server](static-server.md)
**Port: 4001** | Nginx-powered static site hosting
- High-performance serving
- Built documentation hosting
- Caching and compression
- Security headers
### [n8n](n8n.md)
**Port: 5678** | Workflow automation tool
<div class="github-widget" data-repo="n8n-io/n8n"></div>
- Visual workflow editor
- 400+ integrations
- Custom code execution
- Webhook support
### [NocoDB](nocodb.md)
**Port: 8090** | No-code database platform
<div class="github-widget" data-repo="nocodb/nocodb"></div>
- Smart spreadsheet interface
- Form builder and API generation
- Real-time collaboration
- Multi-database support
### [Homepage](homepage.md)
**Port: 3010** | Modern dashboard for all services
<div class="github-widget" data-repo="gethomepage/homepage"></div>
- Service dashboard and monitoring
- Docker integration
- Customizable layout
- Quick search and bookmarks
### [Gitea](gitea.md)
**Port: 3030** | Self-hosted Git service
<div class="github-widget" data-repo="go-gitea/gitea"></div>
- Git repository hosting
- Web-based interface
- Issue tracking
- Pull requests
- Wiki and code review
- Lightweight and easy to deploy
### [Mini QR](mini-qr.md)
**Port: 8089** | Simple QR code generator service
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
- Generate QR codes for text or URLs
- Download QR codes as images
- Simple and fast interface
- No user registration required
### [Map](map.md)
**Port: 3000** | Canvassing and community organizing application
<div class="gitea-widget" data-repo="admin/changemaker.lite"></div>
- Interactive map for door-to-door canvassing
- Location and contact management
- Admin panel and QR code walk sheets
- NocoDB integration for data storage
- User authentication and access control
## Service Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Homepage │ │ Code Server │ │ MkDocs │
│ :3010 │ │ :8888 │ │ :4000 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Static Server │ │ Listmonk │ │ n8n │
│ :4001 │ │ :9000 │ │ :5678 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ NocoDB │ │ PostgreSQL │ │ PostgreSQL │
│ :8090 │ │ (listmonk-db) │ │ (root_db) │
└─────────────────┘ │ :5432 │ │ :5432 │
└─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Map │
│ :3000 │
└─────────────────┘
```

View File

@@ -1,68 +0,0 @@
# Listmonk
<div class="github-widget" data-repo="knadh/listmonk"></div>
Self-hosted newsletter and mailing list manager.
## Overview
Listmonk is a modern, feature-rich newsletter and mailing list manager designed for high performance and easy management. It provides a complete solution for email campaigns, subscriber management, and analytics.
## Features
- Newsletter and email campaign management
- Subscriber list management
- Template system with HTML/markdown support
- Campaign analytics and tracking
- API for integration
- Multi-list support
- Bounce handling
- Privacy-focused design
## Access
- **Default Port**: 9000
- **URL**: `http://localhost:9000`
- **Admin User**: Set via `LISTMONK_ADMIN_USER` environment variable
- **Admin Password**: Set via `LISTMONK_ADMIN_PASSWORD` environment variable
## Configuration
### Environment Variables
- `LISTMONK_ADMIN_USER`: Admin username
- `LISTMONK_ADMIN_PASSWORD`: Admin password
- `POSTGRES_USER`: Database username
- `POSTGRES_PASSWORD`: Database password
- `POSTGRES_DB`: Database name
### Database
Listmonk uses PostgreSQL as its backend database. The database is automatically configured through the docker-compose setup.
### Uploads
- Upload directory: `./assets/uploads`
- Used for media files, templates, and attachments
## Getting Started
1. Access Listmonk at `http://localhost:9000`
2. Log in with your admin credentials
3. Set up your first mailing list
4. Configure SMTP settings for sending emails
5. Import subscribers or create subscription forms
6. Create your first campaign
## Important Notes
- Configure SMTP settings before sending emails
- Set up proper domain authentication (SPF, DKIM) for better deliverability
- Regularly backup your subscriber data and campaigns
- Monitor bounce rates and maintain list hygiene
## Official Documentation
For comprehensive guides and API documentation, visit:
- [Listmonk Documentation](https://listmonk.app/docs/)
- [GitHub Repository](https://github.com/knadh/listmonk)

View File

@@ -1,96 +0,0 @@
# Map
![alt text](map.png)
Interactive map service for geospatial data visualization, powered by NocoDB and Leaflet.js.
## Overview
The Map service provides an interactive web-based map for displaying, searching, and analyzing geospatial data from a NocoDB backend. It supports real-time geolocation, adding new locations, and is optimized for both desktop and mobile use.
## Features
- Interactive map visualization with OpenStreetMap
- Real-time geolocation support
- Add new locations directly from the map
- Auto-refresh every 30 seconds
- Responsive design for mobile devices
- Secure API proxy to protect credentials
- Docker containerization for easy deployment
## Access
- **Default Port**: `${MAP_PORT:-3000}` (default: 3000)
- **URL**: `http://localhost:${MAP_PORT:-3000}`
- **Default Workspace**: `/app/public/`
## Configuration
All configuration is done via environment variables:
| Variable | Description | Default |
|---------------------|------------------------------------|--------------|
| `NOCODB_API_URL` | NocoDB API base URL | Required |
| `NOCODB_API_TOKEN` | API authentication token | Required |
| `NOCODB_VIEW_URL` | Full NocoDB view URL | Required |
| `PORT` | Server port | 3000 |
| `DEFAULT_LAT` | Default map latitude | 53.5461 |
| `DEFAULT_LNG` | Default map longitude | -113.4938 |
| `DEFAULT_ZOOM` | Default map zoom level | 11 |
### Volumes
- `./map/app/public`: Map public assets
## Usage
1. Access the map at `http://localhost:${MAP_PORT:-3000}`
2. Search for locations or addresses
3. Add or view custom markers
4. Analyze geospatial data as needed
## NocoDB Table Setup
### Required Columns
- `geodata` (Text): Format "latitude;longitude"
- `latitude` (Decimal): Precision 10, Scale 8
- `longitude` (Decimal): Precision 11, Scale 8
### Form Fields (as seen in the interface)
- `First Name` (Text): Person's first name
- `Last Name` (Text): Person's last name
- `Email` (Email): Contact email address
- `Unit Number` (Text): Apartment/unit number
- `Support Level` (Single Select):
- 1 - Strong Support (Green)
- 2 - Moderate Support (Yellow)
- 3 - Low Support (Orange)
- 4 - No Support (Red)
- `Address` (Text): Full street address
- `Sign` (Checkbox): Has campaign sign (true/false)
- `Sign Size` (Single Select): Small, Medium, Large
- `Geo-Location` (Text): Formatted as "latitude;longitude"
## API Endpoints
- `GET /api/locations` - Fetch all locations
- `POST /api/locations` - Create new location
- `GET /api/locations/:id` - Get single location
- `PUT /api/locations/:id` - Update location
- `DELETE /api/locations/:id` - Delete location
- `GET /health` - Health check
## Security Considerations
- API tokens are kept server-side only
- CORS is configured for security
- Rate limiting prevents abuse
- Input validation on all endpoints
- Helmet.js for security headers
## Troubleshooting
- Ensure NocoDB table has required columns and valid coordinates
- Check API token permissions and network connectivity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -1,38 +0,0 @@
# Mini QR
<div class="github-widget" data-repo="lyqht/mini-qr"></div>
Simple QR code generator service.
## Overview
Mini QR is a lightweight service for generating QR codes for URLs, text, or other data. It provides a web interface for quick QR code creation and download.
## Features
- Generate QR codes for text or URLs
- Download QR codes as images
- Simple and fast interface
- No user registration required
## Access
- **Default Port**: `${MINI_QR_PORT:-8089}` (default: 8089)
- **URL**: `http://localhost:${MINI_QR_PORT:-8089}`
## Configuration
### Environment Variables
- `QR_DEFAULT_SIZE`: Default size of generated QR codes
- `QR_IMAGE_FORMAT`: Image format (e.g., `png`, `svg`)
### Volumes
- `./configs/mini-qr`: QR code service configuration
## Usage
1. Access Mini QR at `http://localhost:${MINI_QR_PORT:-8089}`
2. Enter the text or URL to encode
3. Download or share the generated QR code

View File

@@ -1,132 +0,0 @@
# MkDocs Material
<div class="github-widget" data-repo="squidfunk/mkdocs-material"></div>
Modern documentation site generator with live preview.
Looking for more info on BNKops code-server integration?
[→ Code Server Configuration](../config/coder.md)
## Overview
MkDocs Material is a powerful documentation framework built on top of MkDocs, providing a beautiful Material Design theme and advanced features for creating professional documentation sites.
## Features
- Material Design theme
- Live preview during development
- Search functionality
- Navigation and organization
- Code syntax highlighting
- Mathematical expressions support
- Responsive design
- Customizable themes and colors
## Access
- **Development Port**: 4000
- **Development URL**: `http://localhost:4000`
- **Live Reload**: Automatically refreshes on file changes
## Configuration
### Main Configuration
Configuration is managed through `mkdocs.yml` in the project root.
### Volumes
- `./mkdocs`: Documentation source files
- `./assets/images`: Shared images directory
### Environment Variables
- `SITE_URL`: Base domain for the site
- `USER_ID`: User ID for file permissions
- `GROUP_ID`: Group ID for file permissions
## Directory Structure
```
mkdocs/
├── mkdocs.yml # Configuration file
├── docs/ # Documentation source
│ ├── index.md # Homepage
│ ├── services/ # Service documentation
│ ├── blog/ # Blog posts
│ └── overrides/ # Template overrides
└── site/ # Built static site
```
## Writing Documentation
### Markdown Basics
- Use standard Markdown syntax
- Support for tables, code blocks, and links
- Mathematical expressions with MathJax
- Admonitions for notes and warnings
### Example Page
```markdown
# Page Title
This is a sample documentation page.
## Section
Content goes here with **bold** and *italic* text.
### Code Example
```python
def hello_world():
print("Hello, World!")
```
!!! note
This is an informational note.
```
## Building and Deployment
### Development
The development server runs automatically with live reload.
### Building Static Site
```bash
docker exec mkdocs-changemaker mkdocs build
```
The built site will be available in the `mkdocs/site/` directory.
## Customization
### Themes and Colors
Customize appearance in `mkdocs.yml`:
```yaml
theme:
name: material
palette:
primary: blue
accent: indigo
```
### Custom CSS
Add custom styles in `docs/stylesheets/extra.css`.
## Official Documentation
For comprehensive MkDocs Material documentation:
- [MkDocs Material](https://squidfunk.github.io/mkdocs-material/)
- [MkDocs Documentation](https://www.mkdocs.org/)
- [Markdown Guide](https://www.markdownguide.org/)

View File

@@ -1,157 +0,0 @@
# n8n
<div class="github-widget" data-repo="n8n-io/n8n"></div>
Workflow automation tool for connecting services and automating tasks.
## Overview
n8n is a powerful workflow automation tool that allows you to connect various apps and services together. It provides a visual interface for creating automated workflows, making it easy to integrate different systems and automate repetitive tasks.
## Features
- Visual workflow editor
- 400+ integrations
- Custom code execution (JavaScript/Python)
- Webhook support
- Scheduled workflows
- Error handling and retries
- User management
- API access
- Self-hosted and privacy-focused
## Access
- **Default Port**: 5678
- **URL**: `http://localhost:5678`
- **Default User Email**: Set via `N8N_DEFAULT_USER_EMAIL`
- **Default User Password**: Set via `N8N_DEFAULT_USER_PASSWORD`
## Configuration
### Environment Variables
- `N8N_HOST`: Hostname for n8n (default: `n8n.${DOMAIN}`)
- `N8N_PORT`: Internal port (5678)
- `N8N_PROTOCOL`: Protocol for webhooks (https)
- `NODE_ENV`: Environment (production)
- `WEBHOOK_URL`: Base URL for webhooks
- `GENERIC_TIMEZONE`: Timezone setting
- `N8N_ENCRYPTION_KEY`: Encryption key for credentials
- `N8N_USER_MANAGEMENT_DISABLED`: Enable/disable user management
- `N8N_DEFAULT_USER_EMAIL`: Default admin email
- `N8N_DEFAULT_USER_PASSWORD`: Default admin password
### Volumes
- `n8n_data`: Persistent data storage
- `./local-files`: Local file access for workflows
## Getting Started
1. Access n8n at `http://localhost:5678`
2. Log in with your admin credentials
3. Create your first workflow
4. Add nodes for different services
5. Configure connections between nodes
6. Test and activate your workflow
## Common Use Cases
### Documentation Automation
- Auto-generate documentation from code comments
- Sync documentation between different platforms
- Notify team when documentation is updated
### Email Campaign Integration
- Connect Listmonk with external data sources
- Automate subscriber management
- Trigger campaigns based on events
### Database Management with NocoDB
- Sync data between NocoDB and external APIs
- Automate data entry and validation
- Create backup workflows for database content
- Generate reports from NocoDB data
### Development Workflows
- Auto-deploy documentation on git push
- Sync code changes with documentation
- Backup automation
### Data Processing
- Process CSV files and import to databases
- Transform data between different formats
- Schedule regular data updates
## Example Workflows
### Simple Webhook to Email
```
Webhook → Email
```
### Scheduled Documentation Backup
```
Schedule → Read Files → Compress → Upload to Storage
```
### Git Integration
```
Git Webhook → Process Changes → Update Documentation → Notify Team
```
## Security Considerations
- Use strong encryption keys
- Secure webhook URLs
- Regularly update credentials
- Monitor workflow executions
- Implement proper error handling
## Integration with Other Services
n8n can integrate with all services in your Changemaker Lite setup:
- **Listmonk**: Manage subscribers and campaigns
- **PostgreSQL**: Read/write database operations
- **Code Server**: File operations and git integration
- **MkDocs**: Documentation generation and updates
## Troubleshooting
### Common Issues
- **Workflow Execution Errors**: Check node configurations and credentials
- **Webhook Issues**: Verify URLs and authentication
- **Connection Problems**: Check network connectivity between services
### Debugging
```bash
# Check container logs
docker logs n8n-changemaker
# Access container shell
docker exec -it n8n-changemaker sh
# Check workflow executions in the UI
# Visit http://localhost:5678 → Executions
```
## Official Documentation
For comprehensive n8n documentation:
- [n8n Documentation](https://docs.n8n.io/)
- [Community Workflows](https://n8n.io/workflows/)
- [Node Reference](https://docs.n8n.io/integrations/builtin/)
- [GitHub Repository](https://github.com/n8n-io/n8n)

View File

@@ -1,163 +0,0 @@
# NocoDB
<div class="github-widget" data-repo="nocodb/nocodb"></div>
No-code database platform that turns any database into a smart spreadsheet.
## Overview
NocoDB is an open-source no-code platform that transforms any database into a smart spreadsheet interface. It provides a user-friendly way to manage data, create forms, build APIs, and collaborate on database operations without requiring extensive technical knowledge.
## Features
- **Smart Spreadsheet Interface**: Transform databases into intuitive spreadsheets
- **Form Builder**: Create custom forms for data entry
- **API Generation**: Auto-generated REST APIs for all tables
- **Collaboration**: Real-time collaboration with team members
- **Access Control**: Role-based permissions and sharing
- **Data Visualization**: Charts and dashboard creation
- **Webhooks**: Integration with external services
- **Import/Export**: Support for CSV, Excel, and other formats
- **Multi-Database Support**: Works with PostgreSQL, MySQL, SQLite, and more
## Access
- **Default Port**: 8090
- **URL**: `http://localhost:8090`
- **Database**: PostgreSQL (dedicated `root_db` instance)
## Configuration
### Environment Variables
- `NOCODB_PORT`: External port mapping (default: 8090)
- `NC_DB`: Database connection string for PostgreSQL backend
### Database Backend
NocoDB uses a dedicated PostgreSQL instance (`root_db`) with the following configuration:
- **Database Name**: `root_db`
- **Username**: `postgres`
- **Password**: `password`
- **Host**: `root_db` (internal container name)
### Volumes
- `nc_data`: Application data and configuration storage
- `db_data`: PostgreSQL database files
## Getting Started
1. **Access NocoDB**: Navigate to `http://localhost:8090`
2. **Initial Setup**: Complete the onboarding process
3. **Create Project**: Start with a new project or connect existing databases
4. **Add Tables**: Import data or create new tables
5. **Configure Views**: Set up different views (Grid, Form, Gallery, etc.)
6. **Set Permissions**: Configure user access and sharing settings
## Common Use Cases
### Content Management
- Create content databases for blogs and websites
- Manage product catalogs and inventories
- Track customer information and interactions
### Project Management
- Task and project tracking systems
- Team collaboration workspaces
- Resource and timeline management
### Data Collection
- Custom forms for surveys and feedback
- Event registration and management
- Lead capture and CRM systems
### Integration with Other Services
NocoDB can integrate well with other Changemaker Lite services:
- **n8n Integration**: Use NocoDB as a data source/destination in automation workflows
- **Listmonk Integration**: Manage subscriber lists and campaign data
- **Documentation**: Store and manage documentation metadata
## API Usage
NocoDB automatically generates REST APIs for all your tables:
```bash
# Get all records from a table
GET http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
# Create a new record
POST http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}
# Update a record
PATCH http://localhost:8090/api/v1/db/data/v1/{project}/table/{table}/{id}
```
## Backup and Data Management
### Database Backup
Since NocoDB uses PostgreSQL, you can backup the database:
```bash
# Backup NocoDB database
docker exec root_db pg_dump -U postgres root_db > nocodb_backup.sql
# Restore from backup
docker exec -i root_db psql -U postgres root_db < nocodb_backup.sql
```
### Application Data
Application settings and metadata are stored in the `nc_data` volume.
## Security Considerations
- Change default database credentials in production
- Configure proper access controls within NocoDB
- Use HTTPS for production deployments
- Regularly backup both database and application data
- Monitor access logs and user activities
## Performance Tips
- Regular database maintenance and optimization
- Monitor memory usage for large datasets
- Use appropriate indexing for frequently queried fields
- Consider database connection pooling for high-traffic scenarios
## Troubleshooting
### Common Issues
**Service won't start**: Check if the PostgreSQL database is healthy
```bash
docker logs root_db
```
**Database connection errors**: Verify database credentials and network connectivity
```bash
docker exec nocodb nc_data nc
```
**Performance issues**: Monitor resource usage and optimize queries
```bash
docker stats nocodb root_db
```
## Official Documentation
For comprehensive guides and advanced features:
- [NocoDB Documentation](https://docs.nocodb.com/)
- [GitHub Repository](https://github.com/nocodb/nocodb)
- [Community Forum](https://community.nocodb.com/)

View File

@@ -1,90 +0,0 @@
# PostgreSQL Database
Reliable database backend for applications.
## Overview
PostgreSQL is a powerful, open-source relational database system. In Changemaker Lite, it serves as the backend database for Listmonk and can be used by other applications requiring persistent data storage.
## Features
- ACID compliance
- Advanced SQL features
- JSON/JSONB support
- Full-text search
- Extensibility
- High performance
- Reliability and data integrity
## Access
- **Default Port**: 5432
- **Host**: `listmonk-db` (internal container name)
- **Database**: Set via `POSTGRES_DB` environment variable
- **Username**: Set via `POSTGRES_USER` environment variable
- **Password**: Set via `POSTGRES_PASSWORD` environment variable
## Configuration
### Environment Variables
- `POSTGRES_USER`: Database username
- `POSTGRES_PASSWORD`: Database password
- `POSTGRES_DB`: Database name
### Health Checks
The PostgreSQL container includes health checks to ensure the database is ready before dependent services start.
### Data Persistence
Database data is stored in a Docker volume (`listmonk-data`) to ensure persistence across container restarts.
## Connecting to the Database
### From Host Machine
You can connect to PostgreSQL from your host machine using:
```bash
psql -h localhost -p 5432 -U [username] -d [database]
```
### From Other Containers
Other containers can connect using the internal hostname `listmonk-db` on port 5432.
## Backup and Restore
### Backup
```bash
docker exec listmonk-db pg_dump -U [username] [database] > backup.sql
```
### Restore
```bash
docker exec -i listmonk-db psql -U [username] [database] < backup.sql
```
## Monitoring
Monitor database health and performance through:
- Container logs: `docker logs listmonk-db`
- Database metrics and queries
- Connection monitoring
## Security Considerations
- Use strong passwords
- Regularly update PostgreSQL version
- Monitor access logs
- Implement regular backups
- Consider network isolation
## Official Documentation
For comprehensive PostgreSQL documentation:
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
- [Docker PostgreSQL Image](https://hub.docker.com/_/postgres)

View File

@@ -1,100 +0,0 @@
# Static Site Server
Nginx-powered static site server for hosting built documentation and websites.
## Overview
The Static Site Server uses Nginx to serve your built documentation and static websites. It's configured to serve the built MkDocs site and other static content with high performance and reliability.
## Features
- High-performance static file serving
- Automatic index file handling
- Gzip compression
- Caching headers
- Security headers
- Custom error pages
- URL rewriting support
## Access
- **Default Port**: 4001
- **URL**: `http://localhost:4001`
- **Document Root**: `/config/www` (mounted from `./mkdocs/site`)
## Configuration
### Environment Variables
- `PUID`: User ID for file permissions (default: 1000)
- `PGID`: Group ID for file permissions (default: 1000)
- `TZ`: Timezone setting (default: Etc/UTC)
### Volumes
- `./mkdocs/site:/config/www`: Static site files
- Built MkDocs site is automatically served
## Usage
1. Build your MkDocs site: `docker exec mkdocs-changemaker mkdocs build`
2. The built site is automatically available at `http://localhost:4001`
3. Any files in `./mkdocs/site/` will be served statically
## File Structure
```
mkdocs/site/ # Served at /
├── index.html # Homepage
├── assets/ # CSS, JS, images
├── services/ # Service documentation
└── search/ # Search functionality
```
## Performance Features
- **Gzip Compression**: Automatic compression for text files
- **Browser Caching**: Optimized cache headers
- **Fast Static Serving**: Nginx optimized for static content
- **Security Headers**: Basic security header configuration
## Custom Configuration
For advanced Nginx configuration, you can:
1. Create custom Nginx config files
2. Mount them as volumes
3. Restart the container
## Monitoring
Monitor the static site server through:
- Container logs: `docker logs mkdocs-site-server-changemaker`
- Access logs for traffic analysis
- Performance metrics
## Troubleshooting
### Common Issues
- **404 Errors**: Ensure MkDocs site is built and files exist in `./mkdocs/site/`
- **Permission Issues**: Check `PUID` and `PGID` settings
- **File Not Found**: Verify file paths and case sensitivity
### Debugging
```bash
# Check container logs
docker logs mkdocs-site-server-changemaker
# Verify files are present
docker exec mkdocs-site-server-changemaker ls -la /config/www
# Test file serving
curl -I http://localhost:4001
```
## Official Documentation
For more information about the underlying Nginx server:
- [LinuxServer.io Nginx](https://docs.linuxserver.io/images/docker-nginx)
- [Nginx Documentation](https://nginx.org/en/docs/)

View File

@@ -1,383 +0,0 @@
# API Reference
Complete REST API reference for Changemaker Lite V2. This section documents all API endpoints, request/response formats, authentication, and error handling.
## Overview
Changemaker Lite V2 provides two REST APIs:
- **Express API** (Port 4000) - Main application API
- **Fastify Media API** (Port 4100) - Media library operations
Both APIs use JSON for request/response bodies and follow RESTful conventions.
## API Documentation
API reference documentation will be added as the API stabilizes. Planned documentation includes:
### Authentication Endpoints
- `POST /api/auth/register` - User registration
- `POST /api/auth/login` - User login
- `POST /api/auth/refresh` - Refresh access token
- `POST /api/auth/logout` - User logout
- `GET /api/auth/me` - Get current user
### User Endpoints
- `GET /api/users` - List users
- `POST /api/users` - Create user
- `GET /api/users/:id` - Get user
- `PATCH /api/users/:id` - Update user
- `DELETE /api/users/:id` - Delete user
### Campaign Endpoints
- `GET /api/campaigns` - List campaigns
- `POST /api/campaigns` - Create campaign
- `GET /api/campaigns/:id` - Get campaign
- `PATCH /api/campaigns/:id` - Update campaign
- `DELETE /api/campaigns/:id` - Delete campaign
- `GET /api/campaigns/public` - List public campaigns
- `POST /api/campaigns/:id/send-email` - Send campaign email
### Location Endpoints
- `GET /api/locations` - List locations
- `POST /api/locations` - Create location
- `GET /api/locations/:id` - Get location
- `PATCH /api/locations/:id` - Update location
- `DELETE /api/locations/:id` - Delete location
- `POST /api/locations/import` - CSV import
- `GET /api/locations/export` - CSV export
- `POST /api/locations/geocode` - Bulk geocode
### Map Endpoints
- `GET /api/cuts` - List cuts
- `POST /api/cuts` - Create cut
- `GET /api/shifts` - List shifts
- `POST /api/shifts` - Create shift
- `GET /api/canvass/session` - Get active session
- `POST /api/canvass/session/start` - Start session
- `POST /api/canvass/visit` - Record visit
### Content Endpoints
- `GET /api/pages` - List pages
- `POST /api/pages` - Create page
- `GET /api/pages/public/:slug` - Get published page
- `GET /api/email-templates` - List templates
- `POST /api/email-templates` - Create template
### Media Endpoints (Port 4100)
- `GET /media-api/videos` - List videos
- `POST /media-api/upload` - Upload video
- `GET /media-api/public/videos` - List public videos
- `POST /media-api/reactions` - Add reaction
## Authentication
All authenticated endpoints require a valid JWT access token in the Authorization header:
```
Authorization: Bearer <access_token>
```
### Token Lifecycle
1. **Login** - POST `/api/auth/login`
- Returns: `accessToken` (15min) + `refreshToken` (7 days)
2. **Access Protected Resource** - Include token in header
- Token verified by `authenticate` middleware
3. **Refresh Token** - POST `/api/auth/refresh`
- Provide: `refreshToken`
- Returns: New `accessToken` + `refreshToken`
4. **Logout** - POST `/api/auth/logout`
- Invalidates refresh token
### Role-Based Access
Endpoints are protected by role requirements:
- **Public** - No authentication required
- **Authenticated** - Any logged-in user
- **Admin** - SUPER_ADMIN, INFLUENCE_ADMIN, or MAP_ADMIN
- **Role-Specific** - Specific role required
## Request Format
### JSON Body
```json
POST /api/campaigns
Content-Type: application/json
Authorization: Bearer <token>
{
"name": "Save the Parks",
"description": "Campaign description",
"published": true
}
```
### Query Parameters
```
GET /api/campaigns?page=1&limit=20&search=parks
```
### Path Parameters
```
GET /api/campaigns/:id
```
## Response Format
### Success Response
```json
{
"id": 1,
"name": "Save the Parks",
"description": "Campaign description",
"published": true,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
```
### Paginated Response
```json
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}
```
### Error Response
```json
{
"error": "Validation error",
"details": "Invalid email format",
"statusCode": 400
}
```
## Status Codes
- **200 OK** - Success
- **201 Created** - Resource created
- **204 No Content** - Success with no body
- **400 Bad Request** - Validation error
- **401 Unauthorized** - Authentication required
- **403 Forbidden** - Insufficient permissions
- **404 Not Found** - Resource not found
- **429 Too Many Requests** - Rate limit exceeded
- **500 Internal Server Error** - Server error
## Rate Limiting
Rate limits vary by endpoint:
- **Auth endpoints** - 10 requests/minute per IP
- **Canvass visits** - 30 requests/minute per IP
- **Public endpoints** - 60 requests/minute per IP
- **Authenticated endpoints** - 120 requests/minute per user
Rate limit headers:
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1640995200
```
## CORS
CORS is enabled for all origins in development:
```javascript
app.use(cors({
origin: '*',
credentials: true,
}));
```
Production should restrict to known domains.
## Validation
Request bodies are validated using Zod schemas. Validation errors return 400 with details:
```json
{
"error": "Validation error",
"details": {
"email": "Invalid email format",
"password": "Password must be at least 12 characters"
},
"statusCode": 400
}
```
## Pagination
List endpoints support pagination:
- **page** - Page number (default: 1)
- **limit** - Items per page (default: 20, max: 100)
Example:
```
GET /api/campaigns?page=2&limit=50
```
## Search & Filtering
List endpoints support search and filtering:
- **search** - Text search (varies by endpoint)
- **filter** - Field-specific filters
Example:
```
GET /api/campaigns?search=parks&published=true
```
## Sorting
List endpoints support sorting:
- **sort** - Field to sort by
- **order** - Sort direction (asc/desc)
Example:
```
GET /api/campaigns?sort=createdAt&order=desc
```
## API Endpoints by Module
### Authentication
- Login, register, refresh, logout, current user
### Users
- CRUD operations, pagination, search, role management
### Settings
- Site settings singleton
### Campaigns
- CRUD, public listing, email sending
### Representatives
- Postal code lookup, cache management
### Responses
- CRUD, verification, upvoting, moderation
### Postal Codes
- Cache service
### Campaign Emails
- Email tracking, statistics
### Email Queue
- Queue monitoring, pause/resume, cleanup
### Locations
- CRUD, CSV import/export, geocoding, NAR import
### Cuts
- CRUD, spatial queries, location assignment
### Shifts
- CRUD, signups, email notifications
### Canvass
- Sessions, visits, routes, dashboard
### Tracking
- GPS tracking (future)
### Map Settings
- Map configuration
### Pages
- CRUD, block library, MkDocs export, public rendering
### Email Templates
- CRUD, versioning (future)
### Media (Port 4100)
- Videos, upload, shared media, reactions, jobs
### Listmonk
- Status, sync, test connection
### Pangolin
- Tunnel management, setup, configuration
### Docs
- MkDocs/Code Server status
### QR
- QR code generation
### Observability
- Prometheus/Grafana/Alertmanager integration
### Services
- Health checks
## OpenAPI Specification
OpenAPI/Swagger documentation is planned for future releases. This will provide:
- Interactive API explorer
- Auto-generated client libraries
- Comprehensive endpoint documentation
- Request/response examples
## Testing
Test API endpoints using:
- **curl** - Command-line HTTP client
- **Postman** - GUI API client
- **HTTPie** - User-friendly CLI
- **Insomnia** - API design/testing tool
Example with curl:
```bash
# Login
curl -X POST http://localhost:4000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"Admin123!"}'
# Get campaigns (with token)
curl http://localhost:4000/api/campaigns \
-H "Authorization: Bearer <token>"
```
## Related Documentation
- [Backend Modules](../backend/modules/index.md)
- [Authentication](../backend/modules/auth.md)
- [Middleware](../backend/middleware/index.md)
- [Development Guide](../development/index.md)
- [Troubleshooting](../troubleshooting/index.md)

View File

@@ -1,800 +0,0 @@
# Authentication Flow
Changemaker Lite V2 uses JWT-based authentication with access and refresh tokens for stateless, scalable authentication.
## Overview
**Key Features:**
- **JWT Tokens** - Stateless authentication (no session storage)
- **Dual Token System** - Short-lived access tokens (15min) + long-lived refresh tokens (7 days)
- **Refresh Token Rotation** - Atomic transaction prevents race conditions
- **Password Policy** - Enforced 12+ characters with complexity requirements
- **Rate Limiting** - 10 requests/min on auth endpoints
- **User Enumeration Prevention** - Consistent 401 responses
- **RBAC** - Role-based access control with 5 roles
## Authentication Architecture
```mermaid
graph TB
subgraph "Client Layer"
Browser[Web Browser]
Storage[LocalStorage<br/>Zustand Persist]
end
subgraph "API Layer"
AuthRoutes[Auth Routes<br/>/api/auth/*]
AuthMiddleware[Auth Middleware<br/>JWT Verification]
RBACMiddleware[RBAC Middleware<br/>Role Check]
end
subgraph "Data Layer"
PG[(PostgreSQL<br/>User + RefreshToken)]
Redis[(Redis<br/>Rate Limiting)]
end
Browser -->|POST /auth/login| AuthRoutes
AuthRoutes -->|Check rate limit| Redis
AuthRoutes -->|Verify credentials| PG
AuthRoutes -->|Generate tokens| AuthRoutes
AuthRoutes -->|Store refresh token| PG
AuthRoutes -->|Return tokens| Browser
Browser -->|Store| Storage
Browser -->|API requests| AuthMiddleware
AuthMiddleware -->|Verify JWT| AuthMiddleware
AuthMiddleware -->|Check role| RBACMiddleware
RBACMiddleware -->|Authorized| Handler[Route Handler]
style AuthRoutes fill:#61dafb,stroke:#333,stroke-width:2px
style AuthMiddleware fill:#ffd700,stroke:#333,stroke-width:2px
style RBACMiddleware fill:#ff6b6b,stroke:#333,stroke-width:2px
```
## User Roles
### Role Hierarchy
```typescript
enum UserRole {
SUPER_ADMIN = 'SUPER_ADMIN', // Full system access
INFLUENCE_ADMIN = 'INFLUENCE_ADMIN', // Campaign management
MAP_ADMIN = 'MAP_ADMIN', // Location + canvassing management
USER = 'USER', // Standard user (limited access)
TEMP = 'TEMP' // Temporary user (public signups, auto-expires)
}
```
### Role Permissions
| Role | Campaign CRUD | Response Moderation | Location Management | User Management | System Settings |
|------|---------------|---------------------|---------------------|-----------------|-----------------|
| **SUPER_ADMIN** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **INFLUENCE_ADMIN** | ✅ | ✅ | ❌ | ❌ | ❌ |
| **MAP_ADMIN** | ❌ | ❌ | ✅ | ❌ | ❌ |
| **USER** | ❌ | ❌ | ❌ | ❌ | ❌ |
| **TEMP** | ❌ | ❌ | ❌ | ❌ | ❌ |
**TEMP User Behavior:**
- Created automatically for public shift signups
- Auto-expires after configured days (`expiresAt`, `expireDays` fields)
- Limited to volunteer canvassing features
- Cannot access admin pages
## Login Flow
### Sequence Diagram
```mermaid
sequenceDiagram
participant User
participant React as Admin GUI
participant Nginx
participant API as Express API
participant Redis
participant PG as PostgreSQL
User->>React: Enter email + password
React->>Nginx: POST /api/auth/login
Nginx->>API: Forward request
API->>Redis: Rate limit check (10/min)
alt Rate limit exceeded
Redis-->>API: Too many requests
API-->>React: 429 Too Many Requests
React-->>User: "Try again later"
else Rate limit OK
API->>PG: SELECT * FROM User WHERE email = ?
alt User not found
PG-->>API: null
API-->>React: 401 Unauthorized
React-->>User: "Invalid credentials"
else User found
PG-->>API: User record
API->>API: bcrypt.compare(password, hash)
alt Password invalid
API-->>React: 401 Unauthorized
React-->>User: "Invalid credentials"
else Password valid
API->>API: Check user status
alt Status SUSPENDED
API-->>React: 403 Forbidden
React-->>User: "Account suspended"
else Status ACTIVE
API->>API: jwt.sign(accessPayload, 15min)
API->>API: jwt.sign(refreshPayload, 7d)
API->>PG: INSERT RefreshToken
API->>PG: UPDATE lastLoginAt
API-->>React: { user, accessToken, refreshToken }
React->>React: Store in Zustand + localStorage
React-->>User: Redirect to dashboard
end
end
end
end
```
### Implementation
**File:** `api/src/modules/auth/auth.service.ts` (lines 22-56)
```typescript
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { prisma } from '../../config/database';
import { loginSchema } from './auth.schemas';
import { incrementMetric } from '../../utils/metrics';
export async function login(credentials: { email: string; password: string }) {
// Validate input
const { email, password } = loginSchema.parse(credentials);
// Find user
const user = await prisma.user.findUnique({
where: { email },
select: {
id: true,
email: true,
password: true,
name: true,
role: true,
status: true,
emailVerified: true,
expiresAt: true
}
});
// User enumeration prevention: consistent 401 response
if (!user) {
throw new Error('Invalid credentials'); // Returns 401
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid credentials'); // Returns 401
}
// Check user status
if (user.status === 'SUSPENDED') {
throw new Error('Account suspended'); // Returns 403
}
if (user.status === 'INACTIVE') {
throw new Error('Account inactive'); // Returns 403
}
// Check TEMP user expiration
if (user.expiresAt && new Date() > user.expiresAt) {
await prisma.user.update({
where: { id: user.id },
data: { status: 'EXPIRED' }
});
throw new Error('Account expired'); // Returns 403
}
// Generate access token (15 minutes)
const accessToken = jwt.sign(
{ id: user.id, email: user.email, role: user.role },
process.env.JWT_ACCESS_SECRET!,
{ expiresIn: '15m' as const }
);
// Generate refresh token (7 days)
const refreshToken = jwt.sign(
{ id: user.id, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' as const }
);
// Store refresh token in database
await prisma.refreshToken.create({
data: {
token: refreshToken,
userId: user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
}
});
// Update last login timestamp
await prisma.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() }
});
// Increment metrics
incrementMetric('cm_login_attempts_total', { status: 'success', role: user.role });
// Return user (no password) + tokens
const { password: _, ...userWithoutPassword } = user;
return {
user: userWithoutPassword,
accessToken,
refreshToken
};
}
```
### Password Policy
**Enforced at Zod schema level:**
**File:** `api/src/modules/auth/auth.schemas.ts` (lines 9-16)
```typescript
import { z } from 'zod';
export const passwordSchema = z
.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one digit');
export const registerSchema = z.object({
email: z.string().email('Invalid email address'),
password: passwordSchema,
name: z.string().min(2, 'Name must be at least 2 characters')
});
export const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(1, 'Password is required')
});
```
**Policy Requirements:**
- Minimum 12 characters
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one digit (0-9)
**Note:** Policy is NOT enforced on login (only on registration/password change) to avoid breaking existing accounts.
## Refresh Token Flow
### Sequence Diagram
```mermaid
sequenceDiagram
participant React as Admin GUI
participant API as Express API
participant PG as PostgreSQL
Note over React: Access token expires (15min)
React->>React: Detect 401 Unauthorized
React->>API: POST /api/auth/refresh
Note right of React: Send refresh token
API->>API: jwt.verify(refreshToken)
alt Token invalid/expired
API-->>React: 401 Unauthorized
React->>React: Clear auth state
React-->>User: Redirect to login
else Token valid
API->>PG: BEGIN TRANSACTION
API->>PG: SELECT RefreshToken WHERE token = ?
alt Token not in database
API->>PG: ROLLBACK
API-->>React: 401 Unauthorized
else Token found
API->>PG: DELETE FROM RefreshToken WHERE token = ?
API->>API: Generate new access token (15min)
API->>API: Generate new refresh token (7d)
API->>PG: INSERT new RefreshToken
API->>PG: COMMIT TRANSACTION
API-->>React: { accessToken, refreshToken }
React->>React: Update stored tokens
React->>React: Retry original request
end
end
```
### Implementation
**File:** `api/src/modules/auth/auth.service.ts` (lines 82-130)
```typescript
export async function refreshTokens(refreshToken: string) {
// Verify refresh token signature
let payload: any;
try {
payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);
} catch (err) {
throw new Error('Invalid refresh token'); // Returns 401
}
// Atomic transaction for token rotation
const result = await prisma.$transaction(async (tx) => {
// Check if refresh token exists in database
const storedToken = await tx.refreshToken.findUnique({
where: { token: refreshToken },
include: { user: true }
});
if (!storedToken) {
throw new Error('Refresh token not found'); // Returns 401
}
// Check expiration
if (new Date() > storedToken.expiresAt) {
// Delete expired token
await tx.refreshToken.delete({
where: { token: refreshToken }
});
throw new Error('Refresh token expired'); // Returns 401
}
// Check user status
if (storedToken.user.status !== 'ACTIVE') {
throw new Error('User account not active'); // Returns 403
}
// Delete old refresh token (rotation)
await tx.refreshToken.delete({
where: { token: refreshToken }
});
// Generate new access token
const newAccessToken = jwt.sign(
{ id: storedToken.user.id, email: storedToken.user.email, role: storedToken.user.role },
process.env.JWT_ACCESS_SECRET!,
{ expiresIn: '15m' as const }
);
// Generate new refresh token
const newRefreshToken = jwt.sign(
{ id: storedToken.user.id, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' as const }
);
// Store new refresh token
await tx.refreshToken.create({
data: {
token: newRefreshToken,
userId: storedToken.user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
return {
accessToken: newAccessToken,
refreshToken: newRefreshToken
};
});
return result;
}
```
**Critical:** Refresh token rotation happens in a **single database transaction** to prevent race conditions (e.g., multiple refresh attempts).
## Frontend Integration
### Zustand Auth Store
**File:** `admin/src/stores/auth.store.ts` (lines 1-100)
```typescript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface User {
id: string;
email: string;
name: string | null;
role: string;
}
interface AuthState {
user: User | null;
accessToken: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
login: (user: User, accessToken: string, refreshToken: string) => void;
logout: () => void;
updateTokens: (accessToken: string, refreshToken: string) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
accessToken: null,
refreshToken: null,
isAuthenticated: false,
login: (user, accessToken, refreshToken) => {
set({
user,
accessToken,
refreshToken,
isAuthenticated: true
});
},
logout: () => {
set({
user: null,
accessToken: null,
refreshToken: null,
isAuthenticated: false
});
},
updateTokens: (accessToken, refreshToken) => {
set({ accessToken, refreshToken });
}
}),
{
name: 'auth-storage', // LocalStorage key
partialize: (state) => ({
user: state.user,
accessToken: state.accessToken,
refreshToken: state.refreshToken,
isAuthenticated: state.isAuthenticated
})
}
)
);
```
### Axios 401 Interceptor
**File:** `admin/src/lib/api.ts` (lines 34-78)
```typescript
import axios from 'axios';
import { useAuthStore } from '../stores/auth.store';
export const api = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json'
}
});
// Request interceptor: Add access token to all requests
api.interceptors.request.use((config) => {
const { accessToken } = useAuthStore.getState();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
// Response interceptor: Handle 401 with token refresh
let isRefreshing = false;
let refreshCallbacks: ((token: string) => void)[] = [];
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// If 401 and we haven't tried refreshing yet
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const { refreshToken, updateTokens, logout } = useAuthStore.getState();
if (!refreshToken) {
logout();
window.location.href = '/login';
return Promise.reject(error);
}
// Deduplicate refresh requests (only one refresh at a time)
if (isRefreshing) {
// Wait for ongoing refresh to complete
return new Promise((resolve) => {
refreshCallbacks.push((token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(api(originalRequest));
});
});
}
isRefreshing = true;
try {
// Refresh tokens
const { data } = await axios.post('/api/auth/refresh', { refreshToken });
// Update stored tokens
updateTokens(data.accessToken, data.refreshToken);
// Retry original request with new token
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
// Resolve queued requests
refreshCallbacks.forEach((callback) => callback(data.accessToken));
refreshCallbacks = [];
return api(originalRequest);
} catch (refreshError) {
// Refresh failed, logout
logout();
window.location.href = '/login';
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
}
);
```
**Key Features:**
- Automatic token refresh on 401
- Deduplicates concurrent refresh requests (callback queue)
- Retries original request after refresh
- Logs out on refresh failure
## Middleware
### JWT Verification
**File:** `api/src/middleware/auth.ts` (lines 1-35)
```typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export interface AuthUser {
id: string;
email: string;
role: string;
}
declare global {
namespace Express {
interface Request {
user?: AuthUser;
}
}
}
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as AuthUser;
req.user = payload; // Attach user to request
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
```
### Role-Based Access Control (RBAC)
**File:** `api/src/middleware/auth.ts` (lines 37-55)
```typescript
export const requireRole = (...allowedRoles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({
error: 'Insufficient permissions',
required: allowedRoles,
current: req.user.role
});
}
next();
};
};
// Block TEMP users from specific routes
export const requireNonTemp = (req: Request, res: Response, next: NextFunction) => {
if (req.user?.role === 'TEMP') {
return res.status(403).json({ error: 'Temporary users cannot access this resource' });
}
next();
};
```
**Usage:**
```typescript
import { authenticate, requireRole, requireNonTemp } from './middleware/auth';
// Require authentication
router.get('/profile', authenticate, getProfile);
// Require specific role
router.post('/campaigns', authenticate, requireRole('SUPER_ADMIN', 'INFLUENCE_ADMIN'), createCampaign);
// Block TEMP users
router.post('/users', authenticate, requireNonTemp, createUser);
```
## Rate Limiting
**File:** `api/src/middleware/rate-limit.ts` (lines 1-45)
```typescript
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { redis } from '../config/redis';
// Auth endpoints: 10 requests per minute
export const authRateLimit = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:auth:',
sendCommand: (...args: string[]) => redis.call(...args)
}),
windowMs: 60 * 1000, // 1 minute
max: 10,
message: 'Too many auth requests, please try again later',
standardHeaders: true,
legacyHeaders: false
});
// Apply to auth routes
import authRoutes from './modules/auth/auth.routes';
app.use('/api/auth/login', authRateLimit);
app.use('/api/auth/register', authRateLimit);
app.use('/api/auth/refresh', authRateLimit);
```
## Security Features
### 1. User Enumeration Prevention
**Problem:** Attackers can enumerate valid emails by observing different error messages.
**Solution:** Consistent 401 response for both "user not found" and "invalid password":
```typescript
if (!user) {
throw new Error('Invalid credentials'); // Same message
}
if (!isValidPassword) {
throw new Error('Invalid credentials'); // Same message
}
```
### 2. Password Hashing
**bcryptjs with automatic salt generation:**
```typescript
import bcrypt from 'bcryptjs';
// Registration
const hashedPassword = await bcrypt.hash(password, 10); // 10 rounds
await prisma.user.create({
data: { email, password: hashedPassword, name, role: 'USER' }
});
// Login
const isValid = await bcrypt.compare(password, user.password);
```
**Rounds:** 10 (balanced between security and performance)
### 3. Refresh Token Rotation
**Prevents replay attacks:**
- Old refresh token deleted immediately after use (atomic transaction)
- New refresh token issued with each refresh
- If old token reused → 401 error
### 4. Token Expiration
| Token Type | Lifetime | Storage | Purpose |
|------------|----------|---------|---------|
| **Access** | 15 minutes | Not stored (JWT only) | API authentication |
| **Refresh** | 7 days | Database + localStorage | Token renewal |
**Short access token lifetime** limits damage if token is stolen.
### 5. Redis Authentication
Redis requires password authentication:
```bash
# .env
REDIS_PASSWORD=strong_password_here
# Redis connection
REDIS_URL=redis://:${REDIS_PASSWORD}@redis-changemaker:6379
```
## Troubleshooting
### Login Fails with Correct Password
**Cause:** User status not ACTIVE, or TEMP user expired.
**Solution:**
```sql
-- Check user status
SELECT email, status, expiresAt FROM "User" WHERE email = 'user@example.com';
-- Activate user
UPDATE "User" SET status = 'ACTIVE' WHERE email = 'user@example.com';
```
### Token Refresh Fails
**Cause:** Refresh token not in database (deleted or expired).
**Solution:**
```sql
-- Check if refresh token exists
SELECT * FROM "RefreshToken" WHERE token = 'token_here';
-- Delete all expired tokens
DELETE FROM "RefreshToken" WHERE "expiresAt" < NOW();
```
### 401 on All Requests
**Cause:** Access token missing, invalid, or expired.
**Debug:**
```bash
# Decode JWT (without verifying signature)
echo "eyJhbG..." | cut -d'.' -f2 | base64 -d | jq
# Check expiration
# Look for "exp" field (Unix timestamp)
```
### Circular Dependency (auth.store ↔ api.ts)
**Problem:** auth.store imports api.ts, api.ts imports auth.store (circular).
**Solution:** Callback registration pattern (already implemented in api.ts lines 34-78).
## Further Reading
- [RBAC Patterns](../backend/middleware/rbac.md) - Advanced role checks
- [Security Model](security.md) - Comprehensive security audit
- [Database Schema](database.md) - User and RefreshToken models
- [Frontend State Management](frontend.md) - Zustand auth store
- [API Reference: Auth](../api-reference/auth.md) - Complete endpoint docs

View File

@@ -1,751 +0,0 @@
# Dual API Architecture
Changemaker Lite V2 uses a dual API architecture with Express.js for main features and Fastify for the media library microservice.
## Why Dual API?
### Performance Isolation
Media operations (video processing, large uploads) are isolated from core platform features:
- **Video uploads** don't block campaign email sending
- **Media job processing** doesn't affect map rendering
- **Large file transfers** have separate connection pools
### Technology Evaluation
V2 evaluates two popular Node.js frameworks side-by-side:
| Feature | Express.js | Fastify |
|---------|-----------|---------|
| **Ecosystem** | Massive (15+ years) | Growing (7+ years) |
| **Performance** | Good | Excellent (2-3x faster) |
| **TypeScript** | Requires @types/* | Native support |
| **Middleware** | Industry standard | Plugin system |
| **Use Case** | General purpose | High-throughput APIs |
### Independent Scaling
Each API can scale independently:
- **Express API** scales with user activity (campaigns, canvassing)
- **Media API** scales with video library size
- Horizontal scaling: run multiple instances behind nginx load balancer
### Clear Service Boundaries
Microservice preparation without full microservices complexity:
- Shared database (PostgreSQL 16)
- Shared cache (Redis)
- Separate codebases (`api/src/server.ts` vs `api/src/media-server.ts`)
- Future: Could split into separate repositories/deployments
## Architecture Diagram
```mermaid
graph TB
subgraph "Client Layer"
Browser[Web Browser]
Mobile[Mobile App]
end
subgraph "Proxy Layer"
Nginx[Nginx Reverse Proxy<br/>Port 80/443]
end
subgraph "API Layer"
Express[Express API<br/>Port 4000<br/>Prisma ORM<br/>27+ Models]
Fastify[Fastify Media API<br/>Port 4100<br/>Drizzle ORM<br/>Media Tables]
end
subgraph "Data Layer"
PG[(PostgreSQL 16<br/>changemaker_v2 DB)]
Redis[(Redis 7<br/>Cache + Queues)]
end
subgraph "External Services"
SMTP[SMTP Server]
Represent[Represent API]
Geocoding[Geocoding APIs]
Listmonk[Listmonk]
end
Browser --> Nginx
Mobile --> Nginx
Nginx -->|/api/* except /api/media/*| Express
Nginx -->|/api/media/*| Fastify
Express --> PG
Express --> Redis
Express --> SMTP
Express --> Represent
Express --> Geocoding
Express --> Listmonk
Fastify --> PG
Fastify --> Redis
style Express fill:#61dafb,stroke:#333,stroke-width:2px
style Fastify fill:#00d562,stroke:#333,stroke-width:2px
style PG fill:#336791,stroke:#333,stroke-width:2px
style Redis fill:#dc382d,stroke:#333,stroke-width:2px
```
## Express API (Main Features)
### Entry Point
**File:** `api/src/server.ts` (234 lines)
```typescript
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { errorHandler } from './middleware/error-handler';
import { authenticate } from './middleware/auth';
import { metricsMiddleware } from './utils/metrics';
const app = express();
// Global middleware
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN, credentials: true }));
app.use(express.json({ limit: '50mb' }));
app.use(metricsMiddleware);
// Health check (no auth)
app.get('/api/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Metrics endpoint (no auth, for Prometheus)
app.get('/api/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
// Route registration (40+ route groups)
app.use('/api/auth', authRoutes);
app.use('/api/users', authenticate, usersRoutes);
app.use('/api/settings', authenticate, settingsRoutes);
app.use('/api/campaigns', campaignsRoutes); // Public + admin routes
app.use('/api/representatives', representativesRoutes);
app.use('/api/responses', responsesRoutes); // Public + admin + moderation
// ... 35+ more route groups
// Global error handler (must be last)
app.use(errorHandler);
const PORT = process.env.API_PORT || 4000;
app.listen(PORT, () => {
logger.info(`Express API listening on port ${PORT}`);
});
```
### Key Features
**14 Feature Modules:**
1. **auth** - JWT login, register, refresh, logout
2. **users** - User CRUD with pagination + search
3. **settings** - Site settings singleton
4. **campaigns** - Campaign CRUD + public routes
5. **representatives** - Represent API integration
6. **responses** - Response wall + moderation + upvoting
7. **email-queue** - BullMQ queue admin
8. **campaign-emails** - Email tracking + stats
9. **postal-codes** - Postal code cache
10. **locations** - Location CRUD + geocoding + NAR import
11. **cuts** - Cut (polygon) CRUD + spatial queries
12. **shifts** - Shift CRUD + signups
13. **canvass** - Volunteer canvassing (sessions, visits, routes)
14. **pages** - Landing page builder (GrapesJS)
**Plus:** email-templates, listmonk, pangolin, docs, qr, services, observability
### Architecture Pattern
**Layered Structure:**
```
api/src/modules/{module}/
├── {module}.routes.ts # Express router + middleware
├── {module}.service.ts # Business logic + database queries
├── {module}.schemas.ts # Zod validation schemas
└── {module}.types.ts # TypeScript interfaces (optional)
```
**Example: Campaign Module**
```typescript
// campaigns.routes.ts
import { Router } from 'express';
import { validate } from '../../middleware/validate';
import { authenticate, requireRole } from '../../middleware/auth';
import { createCampaignSchema, updateCampaignSchema } from './campaigns.schemas';
import * as campaignService from './campaigns.service';
const router = Router();
// Admin routes (auth required)
router.post('/',
authenticate,
requireRole('SUPER_ADMIN', 'INFLUENCE_ADMIN'),
validate(createCampaignSchema),
async (req, res) => {
const campaign = await campaignService.createCampaign(req.body, req.user!.id);
res.status(201).json(campaign);
}
);
// Public routes (no auth)
router.get('/:id', async (req, res) => {
const campaign = await campaignService.getCampaignById(req.params.id);
res.json(campaign);
});
export default router;
```
### ORM: Prisma
**27+ Models** in `api/prisma/schema.prisma`:
```typescript
model Campaign {
id String @id @default(cuid())
slug String @unique
title String
description String? @db.Text
emailSubject String
emailBody String @db.Text
status CampaignStatus @default(DRAFT)
// Feature flags
allowSmtpEmail Boolean @default(true)
showResponseWall Boolean @default(true)
// Audit fields
createdByUserId String?
createdByUser User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
emails CampaignEmail[]
responses RepresentativeResponse[]
customRecipients CustomRecipient[]
}
```
**Connection Pooling:**
Prisma manages connection pool automatically:
```typescript
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Default pool size: 10 connections per instance
// Configure via DATABASE_URL: ?connection_limit=20
```
## Fastify API (Media Library)
### Entry Point
**File:** `api/src/media-server.ts` (104 lines)
```typescript
import Fastify from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import { videosRoutes } from './modules/media/videos/videos.routes';
import { sharedMediaRoutes } from './modules/media/shared-media/shared-media.routes';
import { jobsRoutes } from './modules/media/jobs/jobs.routes';
import { reactionsRoutes } from './modules/media/reactions/reactions.routes';
const fastify = Fastify({
logger: {
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug'
}
});
// Plugins
await fastify.register(cors, {
origin: process.env.CORS_ORIGIN,
credentials: true
});
await fastify.register(helmet);
// Health check
fastify.get('/health', async (request, reply) => {
return { status: 'healthy', timestamp: new Date().toISOString() };
});
// Route registration
fastify.register(videosRoutes, { prefix: '/api/media/videos' });
fastify.register(sharedMediaRoutes, { prefix: '/api/media/shared' });
fastify.register(jobsRoutes, { prefix: '/api/media/jobs' });
fastify.register(reactionsRoutes, { prefix: '/api/media/reactions' });
const PORT = Number(process.env.MEDIA_API_PORT) || 4100;
await fastify.listen({ port: PORT, host: '0.0.0.0' });
fastify.log.info(`Fastify Media API listening on port ${PORT}`);
```
### Key Features
**4 Feature Modules:**
1. **videos** - Video CRUD, metadata, tags, deduplication
2. **shared-media** - Public gallery categories (videos, curated, compilations, etc.)
3. **jobs** - Job queue monitoring (pending, running, completed, failed)
4. **reactions** - Reaction system (6 standard emojis: like, love, laugh, wow, sad, angry)
### Architecture Pattern
**Plugin-Based:**
```typescript
// videos.routes.ts
import { FastifyPluginAsync } from 'fastify';
import { verifyJWT } from '../../middleware/auth';
import { getVideosSchema, createVideoSchema } from './videos.schemas';
export const videosRoutes: FastifyPluginAsync = async (fastify) => {
// Middleware: JWT verification
fastify.addHook('onRequest', verifyJWT);
// GET /api/media/videos
fastify.get('/', {
schema: getVideosSchema,
handler: async (request, reply) => {
const videos = await getVideos(request.query);
return videos;
}
});
// POST /api/media/videos
fastify.post('/', {
schema: createVideoSchema,
handler: async (request, reply) => {
const video = await createVideo(request.body);
return reply.status(201).send(video);
}
});
};
```
### ORM: Drizzle
**Media Tables** in `api/src/modules/media/db/schema.ts`:
```typescript
import { pgTable, serial, text, integer, boolean, timestamp, jsonb } from 'drizzle-orm/pg-core';
export const videos = pgTable('videos', {
id: serial('id').primaryKey(),
path: text('path').unique().notNull(),
filename: text('filename').notNull(),
producer: text('producer'),
creator: text('creator'),
title: text('title'),
durationSeconds: integer('duration_seconds'),
width: integer('width'),
height: integer('height'),
orientation: text('orientation'), // 'landscape' | 'portrait' | 'square'
hasAudio: boolean('has_audio').default(true),
fileSize: integer('file_size'),
thumbnailPath: text('thumbnail_path'),
tags: jsonb('tags').$type<string[]>(),
isValid: boolean('is_valid').default(true),
createdAt: timestamp('created_at').defaultNow(),
}, (table) => ({
orientationIdx: index('idx_orientation').on(table.orientation),
producerIdx: index('idx_producer').on(table.producer),
}));
```
**Connection:**
Drizzle uses the same PostgreSQL connection pool:
```typescript
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10
});
export const db = drizzle(pool);
```
## Request Flow
### Public Campaign Email Submission
```mermaid
sequenceDiagram
participant User as User Browser
participant Nginx
participant React as Admin GUI
participant Express as Express API
participant PG as PostgreSQL
participant Redis
participant BullMQ
participant SMTP
User->>React: Visit /campaigns/123
React->>Nginx: GET /campaigns/123
Nginx->>React: Serve React app
React->>Nginx: GET /api/campaigns/123
Nginx->>Express: Forward to Express
Express->>PG: SELECT campaign
PG-->>Express: Campaign data
Express-->>React: Campaign JSON
React-->>User: Render page
User->>React: Submit email form
React->>Nginx: POST /api/campaigns/123/send-email
Nginx->>Express: Forward to Express
Express->>Express: Rate limit check (30/hour)
Express->>PG: INSERT CampaignEmail
Express->>BullMQ: Enqueue job
BullMQ->>Redis: Add job to queue
Express-->>React: Success response
React-->>User: "Email queued"
BullMQ->>Express: Process job (worker)
Express->>PG: SELECT email + campaign
Express->>Express: Build SMTP message
Express->>SMTP: Send email
SMTP-->>Express: Delivery confirmed
Express->>PG: UPDATE status = SENT
Express->>Redis: Increment cm_emails_sent_total
```
### Admin Media Upload
```mermaid
sequenceDiagram
participant Admin as Admin Browser
participant Nginx
participant Fastify as Fastify Media API
participant PG as PostgreSQL
participant FS as File System
Admin->>Nginx: POST /api/media/videos (10GB file)
Nginx->>Fastify: Stream upload (no buffering)
Fastify->>FS: Save to /media/videos/
Fastify->>PG: INSERT video metadata
PG-->>Fastify: Video record
Fastify-->>Admin: { id, path, thumbnail }
```
**Key Difference:**
- Express handles small JSON payloads (campaigns, locations, users)
- Fastify handles large file uploads (streaming, no buffering)
## Shared Resources
### PostgreSQL Database
**Single Database, Multiple Schemas:**
- **Prisma Tables** — Main schema (User, Campaign, Location, etc.)
- **Drizzle Tables** — Media schema (videos, jobs, reactions)
Both ORMs connect to the same `changemaker_v2` database:
```bash
DATABASE_URL=postgresql://changemaker:password@v2-postgres:5432/changemaker_v2
```
**No Conflicts:**
- Prisma manages its own schema via migrations (`npx prisma migrate`)
- Drizzle manages media tables via `npx drizzle-kit push`
- Tables don't overlap (different prefixes)
### Redis Cache
Both APIs use Redis for:
- **Caching** — Postal codes (Express), video metadata (Fastify)
- **Rate Limiting** — Redis-backed limits (Express: 30/hour, Fastify: 100/min)
- **BullMQ Queues** — Email queue (Express), job queue (Fastify)
```typescript
// Shared Redis connection
import Redis from 'ioredis';
export const redis = new Redis({
host: 'redis-changemaker',
port: 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3
});
```
### JWT Authentication
Both APIs verify the same JWT tokens:
```typescript
// Express: api/src/middleware/auth.ts
import jwt from 'jsonwebtoken';
export const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
req.user = payload; // { id, email, role }
next();
};
// Fastify: api/src/modules/media/middleware/auth.ts
import jwt from 'jsonwebtoken';
export const verifyJWT = async (request, reply) => {
const token = request.headers.authorization?.split(' ')[1];
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
request.user = payload;
};
```
**Shared Secret:** `JWT_ACCESS_SECRET` environment variable
## Nginx Routing
### Location Block Ordering
**Critical:** Media API location must come BEFORE general API location:
```nginx
server {
listen 80;
server_name api.cmlite.org;
# Media API (longest prefix first)
location /api/media/ {
proxy_pass http://changemaker-media-api:4100;
client_max_body_size 10G;
}
# Express API (catch-all)
location /api/ {
proxy_pass http://changemaker-v2-api:4000;
}
}
```
**Why Order Matters:**
Nginx matches longest prefix first. If `/api/` came first, it would match `/api/media/videos` and route to Express (wrong).
### Subdomain Routing (Production)
```nginx
# Express API
server {
listen 80;
server_name api.cmlite.org;
location / {
proxy_pass http://changemaker-v2-api:4000;
}
}
# Fastify Media API
server {
listen 80;
server_name media.cmlite.org;
location / {
proxy_pass http://changemaker-media-api:4100;
}
}
```
## Performance Comparison
### Benchmarks (Internal Testing)
**Simple GET Request (JSON response):**
| Framework | Requests/sec | Latency p95 | Memory |
|-----------|--------------|-------------|--------|
| Express | 12,500 | 35ms | 150MB |
| Fastify | 28,000 | 15ms | 120MB |
**Large Upload (1GB file):**
| Framework | Upload Time | Memory Peak | CPU Usage |
|-----------|-------------|-------------|-----------|
| Express | 45s | 450MB | 85% |
| Fastify | 38s | 280MB | 60% |
**Real-World Usage:**
- Express handles 95% of requests (campaigns, users, locations)
- Fastify handles 5% of requests (video uploads, media library)
- Both run comfortably on single-core containers
## Future: Full Microservices
The dual API design prepares for future microservices migration:
### Potential Split
```
├── campaign-service/ # Express API (Influence module)
├── map-service/ # Express API (Map module)
├── media-service/ # Fastify API (Media module)
├── auth-service/ # Shared authentication
└── api-gateway/ # Nginx or Kong
```
### Benefits
- **Independent deployment** — Ship campaign features without redeploying map
- **Technology flexibility** — Use Go for high-throughput, Python for ML
- **Team ownership** — Separate teams own separate services
- **Fault isolation** — Media service crash doesn't affect campaigns
### Trade-offs
- **Operational complexity** — More containers, more monitoring
- **Network latency** — Inter-service calls over HTTP
- **Data consistency** — Distributed transactions harder
- **Development overhead** — Multiple repos, versioning
**V2 Strategy:** Keep dual API until scaling requires split (likely 10,000+ users).
## Development Workflow
### Running Both APIs
```bash
# Terminal 1: Express API
cd api && npm run dev # Port 4000
# Terminal 2: Fastify Media API
cd api && npm run dev:media # Port 4100
# Terminal 3: Admin GUI
cd admin && npm run dev # Port 3000
```
### Docker Compose
```bash
# Start both APIs
docker compose up -d api media-api
# View logs
docker compose logs -f api
docker compose logs -f media-api
# Rebuild after dependency changes
docker compose build api media-api
docker compose up -d api media-api
```
## Monitoring
Both APIs expose Prometheus metrics:
- **Express:** `http://localhost:4000/api/metrics`
- **Fastify:** `http://localhost:4100/metrics`
**Custom Metrics:**
```typescript
// Express: api/src/utils/metrics.ts
import client from 'prom-client';
export const httpRequestTotal = new client.Counter({
name: 'http_request_total',
help: 'Total HTTP requests',
labelNames: ['method', 'route', 'status']
});
export const emailsSentTotal = new client.Counter({
name: 'cm_emails_sent_total',
help: 'Total campaign emails sent'
});
// Fastify: api/src/modules/media/metrics.ts
export const mediaUploadsTotal = new client.Counter({
name: 'cm_media_uploads_total',
help: 'Total media uploads',
labelNames: ['type']
});
```
Prometheus scrapes both endpoints every 15 seconds.
## Troubleshooting
### Media API Returns 404
**Cause:** Nginx routing issue (order of location blocks).
**Fix:** Ensure `/api/media/` comes BEFORE `/api/` in nginx config.
### Large Upload Fails (413)
**Cause:** `client_max_body_size` too small.
**Fix:** Increase in nginx config:
```nginx
location /api/media/ {
client_max_body_size 20G; # Increase from default
}
```
### Connection Pool Exhausted
**Cause:** Too many concurrent requests, not enough DB connections.
**Fix:** Increase connection limit in `DATABASE_URL`:
```bash
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=20
```
Or reduce pool size per API instance (if running multiple):
```typescript
// Prisma
datasource db {
url = env("DATABASE_URL") // Add ?connection_limit=5 for smaller pool
}
// Drizzle
const pool = new Pool({ max: 5 });
```
### JWT Verification Fails Across APIs
**Cause:** Different `JWT_ACCESS_SECRET` values.
**Fix:** Ensure both APIs use the same secret:
```bash
# .env
JWT_ACCESS_SECRET=<same-value-for-both>
```
## Further Reading
- [Database Architecture](database.md) — Prisma vs Drizzle schemas
- [Authentication Flow](authentication.md) — JWT implementation
- [Monitoring Stack](monitoring.md) — Prometheus metrics
- [Nginx Configuration](../deployment/nginx.md) — Reverse proxy setup
- [Scaling Strategies](../deployment/scaling.md) — Horizontal scaling

View File

@@ -1,590 +0,0 @@
# V2 Architecture Overview
Changemaker Lite V2 is built on a modern microservices architecture with a dual API design, React admin interface, and comprehensive observability.
## System Architecture
```mermaid
graph TB
subgraph "User Access"
Browser[Web Browser]
VolunteerApp[Volunteer Mobile]
end
subgraph "Nginx Reverse Proxy"
Nginx[Nginx<br/>Subdomain Router]
end
subgraph "Frontend Layer"
AdminGUI[Admin GUI<br/>React + Vite + Ant Design<br/>Port 3000]
PublicPages[Public Pages<br/>Dark Theme]
VolunteerPortal[Volunteer Portal<br/>GPS Canvassing]
end
subgraph "Backend Layer - Dual API"
ExpressAPI[Express API<br/>Main Features<br/>Port 4000<br/>Prisma ORM]
FastifyAPI[Fastify API<br/>Media Library<br/>Port 4100<br/>Drizzle ORM]
end
subgraph "Data Layer"
Postgres[(PostgreSQL 16<br/>27+ Models)]
Redis[(Redis<br/>Cache + Queues)]
end
subgraph "Job Processing"
EmailQueue[BullMQ<br/>Email Queue]
GeocodeQueue[BullMQ<br/>Geocoding Queue]
end
subgraph "External Services"
SMTP[SMTP Server<br/>Email Delivery]
Represent[Represent API<br/>Canadian Reps]
Geocoding[Geocoding Providers<br/>6 Services]
Listmonk[Listmonk<br/>Newsletter Platform]
end
subgraph "Observability"
Prometheus[Prometheus<br/>Metrics]
Grafana[Grafana<br/>Dashboards]
Alertmanager[Alertmanager<br/>Notifications]
end
Browser --> Nginx
VolunteerApp --> Nginx
Nginx --> AdminGUI
Nginx --> PublicPages
Nginx --> VolunteerPortal
AdminGUI --> ExpressAPI
AdminGUI --> FastifyAPI
PublicPages --> ExpressAPI
VolunteerPortal --> ExpressAPI
ExpressAPI --> Postgres
ExpressAPI --> Redis
ExpressAPI --> EmailQueue
ExpressAPI --> GeocodeQueue
ExpressAPI --> Represent
ExpressAPI --> Geocoding
ExpressAPI --> Listmonk
ExpressAPI --> Prometheus
FastifyAPI --> Postgres
FastifyAPI --> Redis
FastifyAPI --> Prometheus
EmailQueue --> Redis
EmailQueue --> SMTP
GeocodeQueue --> Redis
GeocodeQueue --> Geocoding
Prometheus --> Grafana
Prometheus --> Alertmanager
```
## Core Components
### 1. Nginx Reverse Proxy
**Purpose**: Routes HTTP requests to appropriate services based on subdomain
**Subdomains**:
- `app.cmlite.org` → Admin GUI (React)
- `api.cmlite.org` → Express API (main features)
- `media.cmlite.org` → Fastify API (video library)
- `db.cmlite.org` → NocoDB (data browser)
- `docs.cmlite.org` → MkDocs (documentation)
- `listmonk.cmlite.org` → Listmonk (newsletter)
- `grafana.cmlite.org` → Grafana (monitoring)
- And 8 more service subdomains...
**Configuration**: `/nginx/conf.d/`
[Learn more →](networking.md)
### 2. Frontend Layer
#### Admin GUI (Port 3000)
- **Framework**: React 19 with Vite build tool
- **UI Library**: Ant Design 5 (Table, Form, Modal, Drawer components)
- **State Management**: Zustand stores (auth, canvass)
- **Routing**: React Router v6
- **HTTP Client**: Axios with 401 refresh interceptor
**Structure**:
- 32 admin pages (campaigns, locations, users, settings, etc.)
- 6 public pages (campaign view, response wall, map, shifts)
- 4 volunteer portal pages (canvassing, assignments, activity)
- Shared components (map, canvass, GrapesJS editor)
[Learn more →](frontend.md)
#### Public Pages
- Dark blue/teal theme (consistent with V1 branding)
- No authentication required
- Mobile-responsive layouts
- Public campaign submission
- Response wall with upvoting
- Public map with location markers
- Shift signup forms
#### Volunteer Portal
- Top navigation layout
- Mobile-optimized (hamburger menu)
- GPS-tracked canvassing
- Full-screen map interface
- Visit recording forms
- Activity tracking
### 3. Backend Layer - Dual API Design
#### Express API (Port 4000)
**Main application server** handling core features:
**14 Feature Modules**:
1. **auth** - JWT login, register, refresh, logout
2. **users** - User CRUD with pagination
3. **settings** - Site settings singleton
4. **campaigns** - Campaign CRUD + public routes
5. **representatives** - Represent API integration
6. **responses** - Response wall + moderation
7. **email-queue** - BullMQ queue admin
8. **campaign-emails** - Email tracking + stats
9. **postal-codes** - Postal code cache
10. **locations** - Location CRUD + geocoding + NAR import
11. **cuts** - Cut (polygon) CRUD + spatial queries
12. **shifts** - Shift CRUD + signups
13. **canvass** - Volunteer canvassing (sessions, visits, routes)
14. **pages** - Landing page builder (GrapesJS)
**Plus**: email-templates, listmonk, pangolin, docs, qr, services, observability
**ORM**: Prisma (27+ models)
**Architecture**:
- Layered structure (routes → services → database)
- Zod schema validation
- Role-based access control (RBAC)
- Error handling middleware
- Winston logging
[Learn more →](dual-api.md)
#### Fastify API (Port 4100)
**Specialized microservice** for media library:
**Features**:
- Video CRUD (title, duration, orientation, producer)
- Shared media (public gallery categories)
- Lock/unlock system (public visibility control)
- Reaction system (6 standard emojis)
- Job queue monitoring
- Bulk operations
**ORM**: Drizzle (lightweight schema-first)
**Why Separate?**:
- Performance isolation (video ops don't slow main API)
- Different ORM evaluation (Drizzle vs Prisma)
- Independent scaling
- Clear service boundaries
**Shared Resources**:
- Same PostgreSQL database (different schemas)
- Same Redis instance
- Reuses JWT_ACCESS_SECRET for auth
[Learn more →](dual-api.md)
### 4. Data Layer
#### PostgreSQL 16
**Primary database** with two ORM schemas:
**Prisma Schema** (27+ models):
- User, RefreshToken (auth)
- Campaign, Representative, Response, CampaignEmail (influence)
- Location, Cut, Shift, ShiftSignup (map)
- CanvassSession, CanvassVisit, TrackingSession, TrackPoint (canvass)
- LandingPage, PageBlock, EmailTemplate (content)
- SiteSettings, MapSettings (config)
**Drizzle Schema** (media tables):
- videos
- shared_media
- reactions
- jobs
**Indexes**: Optimized for common queries (userId, campaignId, cutId, etc.)
[Learn more →](database.md)
#### Redis
**Multi-purpose cache and queue backend**:
- **Caching**: Postal codes (7-day TTL), representatives
- **Rate Limiting**: Per-endpoint limits (Redis-backed)
- **BullMQ Queues**: Email sending, bulk geocoding
- **Sessions**: Future session storage (if needed)
**Authentication**: Required (`REDIS_PASSWORD` env var)
### 5. Job Processing
#### BullMQ Queues
**Async job processing** for long-running operations:
**Email Queue**:
- Campaign email sending (SMTP)
- Email verification (double opt-in)
- Confirmation emails (shift signups)
- Retry logic (exponential backoff)
- Rate limiting (avoid spam flagging)
**Geocoding Queue**:
- Bulk address geocoding
- Multi-provider fallback (6 services)
- Rate limit compliance (500 jobs/min)
- Result caching
**Queue Management**:
- Admin routes for pause/resume
- Job status monitoring
- Failed job inspection
- Queue metrics (Prometheus)
### 6. External Services
#### SMTP Server
Email delivery for:
- Campaign advocacy emails
- Email verification
- Password reset
- Shift confirmation
- Admin notifications
**Dev Mode**: MailHog captures emails (`EMAIL_TEST_MODE=true`)
#### Represent API
Canadian elected representative lookup:
- Postal code → MPs, MPPs, councillors
- Caching (7-day TTL per postal code)
- Fallback to cached data on API errors
#### Geocoding Providers
Multi-provider geocoding with fallback:
1. Nominatim (OpenStreetMap, free)
2. Mapbox (requires API key, best accuracy)
3. ArcGIS (free tier available)
4. Photon (OSM-based, no key required)
5. Google (requires API key, high cost)
6. LocationIQ (requires API key, generous free tier)
**Strategy**: Try each provider in order until success
#### Listmonk Newsletter Platform
Email marketing integration:
- Sync participants/locations/users → subscriber lists
- Newsletter campaigns (separate from advocacy emails)
- API integration (basic auth)
- Health monitoring
### 7. Observability Stack
#### Prometheus
**Metrics collection** with custom instrumentation:
**12 Custom Metrics** (`cm_*` prefix):
- `cm_api_uptime_seconds` - API availability
- `cm_email_queue_size` - Queue depth
- `cm_email_sent_total` - Email delivery count
- `cm_geocode_success_rate` - Geocoding quality
- `cm_active_canvass_sessions` - Live canvassing
- And 7 more domain-specific metrics...
**HTTP Metrics**:
- `http_request_total` - Total requests
- `http_request_duration_seconds` - Latency histogram
- `http_request_errors_total` - Error count
**Scrape Targets**:
- Express API (`:4000/metrics`)
- Fastify API (`:4100/metrics`)
- Redis Exporter
- Node Exporter (host metrics)
- cAdvisor (container metrics)
[Learn more →](monitoring.md)
#### Grafana
**Visualization dashboards**:
1. **Application Overview** - API metrics, queue stats, sessions
2. **Infrastructure** - Container metrics, host resources, Redis
3. **Alerts & SLOs** - Error budgets, SLI tracking
**Auto-provisioned**: Dashboards in `/configs/grafana/`
#### Alertmanager
**Alert routing and notifications**:
**12 Alert Rules**:
- High error rate (>5% for 5 minutes)
- Email queue stuck (no jobs processed in 10 minutes)
- Service down (health check fails)
- Database connection pool exhausted
- Redis unavailable
- And 7 more critical conditions...
**Notification Channels**:
- Gotify (self-hosted push notifications)
- Email (SMTP)
- Webhook (custom integrations)
## Request Lifecycle
### Example: Public Campaign Email Submission
```mermaid
sequenceDiagram
participant User as User Browser
participant Nginx
participant Admin as Admin GUI
participant Express as Express API
participant DB as PostgreSQL
participant Redis
participant Queue as BullMQ
participant SMTP as SMTP Server
participant Rep as Represent API
User->>Nginx: Visit /campaigns/123
Nginx->>Admin: Route to React app
Admin->>Express: GET /api/campaigns/123 (public)
Express->>DB: Query campaign
DB-->>Express: Campaign data
Express-->>Admin: Campaign JSON
Admin-->>User: Render campaign page
User->>Admin: Enter postal code + submit
Admin->>Express: POST /api/postal-codes (lookup)
Express->>Redis: Check cache
Redis-->>Express: Cache miss
Express->>Rep: GET /representatives/postal-code
Rep-->>Express: Representative list
Express->>Redis: Cache for 7 days
Express-->>Admin: Representatives JSON
Admin-->>User: Show rep selection
User->>Admin: Select rep + write email + submit
Admin->>Express: POST /api/responses (create)
Express->>DB: Insert response
Express->>Queue: Enqueue verification email
Express->>DB: Insert campaign email record
DB-->>Express: Response created
Express-->>Admin: Success response
Admin-->>User: Show success message
Queue->>SMTP: Send verification email
SMTP-->>Queue: Delivery confirmed
User->>User: Click verification link (email)
User->>Nginx: GET /verify-response/:token
Nginx->>Admin: Route to React app
Admin->>Express: POST /api/responses/:id/verify
Express->>DB: Update response (verified=true)
Express->>Queue: Enqueue campaign email to rep
DB-->>Express: Response verified
Express-->>Admin: Success
Admin-->>User: Email sent confirmation
Queue->>SMTP: Send campaign email to rep
SMTP-->>Queue: Delivery confirmed
```
## Technology Decisions
### Why TypeScript?
- Type safety reduces runtime errors
- Better IDE support and autocomplete
- Easier refactoring
- Self-documenting code
### Why Prisma + Drizzle?
- **Prisma**: Great for complex models, migrations, auto-generated types
- **Drizzle**: Lightweight, perfect for simple media tables
- Evaluate both ORMs in production
### Why Dual API?
- **Separation of concerns**: Media ops isolated from core features
- **Performance**: Video processing doesn't block main API
- **Scalability**: Independent horizontal scaling
- **Technology evaluation**: Compare Express vs Fastify
### Why JWT over Sessions?
- Stateless (scales horizontally)
- No session storage overhead
- Works across multiple API servers
- Standard claims (iat, exp, sub)
### Why BullMQ over Bull?
- Better TypeScript support
- Improved performance
- Active maintenance
- Better documentation
### Why PostgreSQL over NoSQL?
- Complex relational data (campaigns, locations, users)
- ACID transactions (critical for email queue)
- Full-text search
- Spatial queries (PostGIS for future geo features)
## Deployment Architecture
### Docker Compose
All services orchestrated in `docker-compose.yml`:
**Profiles**:
- `default`: Core services (postgres, redis, api, admin, nginx)
- `monitoring`: Prometheus, Grafana, Alertmanager, exporters
**Networks**:
- `changemaker-lite` bridge network
- Service discovery via container names
**Volumes**:
- PostgreSQL data persistence
- Redis data persistence
- Uploads directory
- Logs directory
[Learn more →](../deployment/docker-compose.md)
### Nginx Routing
**Subdomain-based routing**:
```nginx
# Admin GUI
server {
server_name app.cmlite.org;
location / {
proxy_pass http://admin:3000;
}
}
# Express API
server {
server_name api.cmlite.org;
location / {
proxy_pass http://api:4000;
}
}
# Fastify Media API
server {
server_name media.cmlite.org;
location / {
proxy_pass http://media-api:4100;
}
}
```
[Learn more →](networking.md)
## Security Architecture
### Authentication Flow
```mermaid
sequenceDiagram
participant Client
participant API as Express API
participant DB as PostgreSQL
participant Redis
Client->>API: POST /api/auth/login
API->>DB: Verify credentials
DB-->>API: User record
API->>DB: Create refresh token (expires 7d)
API->>Redis: Rate limit check
API-->>Client: Access token (15min) + Refresh token (7d)
Note over Client: Access token expires
Client->>API: POST /api/auth/refresh
API->>DB: Validate refresh token
DB-->>API: Token valid
API->>DB: Rotate refresh token (transaction)
API-->>Client: New access token + New refresh token
```
**Features**:
- bcrypt password hashing (12+ chars, complexity requirements)
- JWT access tokens (15min expiry)
- Refresh tokens (7 days, stored in DB, rotated on use)
- Rate limiting (10 requests/min on auth endpoints)
- User enumeration prevention (401 not 404)
- RBAC middleware (requireRole, requireNonTemp)
[Learn more →](authentication.md)
### Security Layers
1. **Network**: Nginx rate limiting, fail2ban
2. **Application**: Input validation (Zod schemas), RBAC
3. **Data**: Encrypted fields (ENCRYPTION_KEY), SQL injection prevention (Prisma)
4. **Transport**: HTTPS only (production), HSTS headers
[Learn more →](security.md)
## Scalability Considerations
### Horizontal Scaling
- **Stateless APIs**: JWT auth allows multiple API instances
- **Redis-backed queues**: Share job queues across workers
- **Database connection pooling**: Prisma manages connections
- **Nginx load balancing**: Distribute requests across API instances
### Vertical Scaling
- Increase container resources (CPU, memory)
- Optimize database queries (indexes, query planning)
- Redis memory limits (LRU eviction policy)
### Bottlenecks
- **PostgreSQL**: Single primary (future: read replicas)
- **Redis**: Single instance (future: Redis Cluster)
- **File uploads**: Local disk (future: S3-compatible storage)
## Monitoring & Observability
### Golden Signals
1. **Latency**: Request duration histograms
2. **Traffic**: Request rate by endpoint
3. **Errors**: Error rate (5xx responses)
4. **Saturation**: Database connections, Redis memory, queue depth
### SLOs (Service Level Objectives)
- **Availability**: 99.9% uptime (8.76 hours downtime/year)
- **Latency**: p95 < 500ms, p99 < 1000ms
- **Error Rate**: < 0.1% (1 error per 1000 requests)
### Alerting Strategy
- **Critical**: Page on-call (service down, database unavailable)
- **Warning**: Create ticket (queue growing, elevated errors)
- **Info**: Log only (slow query, cache miss)
[Learn more →](monitoring.md)
## Further Reading
- [Dual API Architecture](dual-api.md) - Express + Fastify design
- [Database Schema](database.md) - Complete ER diagram
- [Authentication Flow](authentication.md) - JWT security model
- [Frontend Architecture](frontend.md) - React + Vite + Ant Design
- [Networking](networking.md) - Nginx routing and subdomains
- [Security Model](security.md) - Comprehensive security audit
- [Monitoring Stack](monitoring.md) - Prometheus + Grafana + Alertmanager
- [Data Flow](data-flow.md) - Request lifecycle examples
---
**Next**: [Set up your development environment →](../development/local-setup.md)

View File

@@ -1,75 +0,0 @@
# Backend Overview
The Changemaker Lite V2 backend is a dual-API architecture built with TypeScript, providing a robust foundation for campaign management, mapping, and media services.
## Architecture
The backend consists of two complementary API servers:
- **Express API** (Port 4000) - Main V2 features with Prisma ORM + PostgreSQL
- **Fastify Media API** (Port 4100) - Video library with Drizzle ORM (shared database)
Both APIs share a common PostgreSQL 16 database but use different ORM approaches for their specific needs. The Express API handles the majority of business logic, while the Fastify API is optimized for media operations.
## Key Components
### [Modules](modules/index.md)
Backend modules provide feature-specific functionality across authentication, campaigns, locations, media, and more. Each module follows a consistent pattern with schemas, services, and routes.
### [Services](services/index.md)
Shared services provide cross-cutting concerns like email delivery, geocoding, queue management, and external API integrations.
### [Middleware](middleware/index.md)
Middleware components handle authentication, authorization, rate limiting, validation, and error handling across all API endpoints.
### [Utilities](utilities/index.md)
Utility modules provide common functionality for spatial calculations, logging, metrics collection, and data processing.
## Technology Stack
- **Runtime:** Node.js 20+ with TypeScript 5.x
- **Main Framework:** Express.js (TypeScript)
- **Media Framework:** Fastify (TypeScript)
- **ORMs:**
- Prisma (main API)
- Drizzle (media API)
- **Database:** PostgreSQL 16
- **Cache/Queue:** Redis 7 with BullMQ
- **Validation:** Zod schemas
- **Authentication:** JWT with bcrypt
## API Structure
```
api/
├── src/
│ ├── server.ts # Express API entry point (port 4000)
│ ├── media-server.ts # Fastify media API (port 4100)
│ ├── config/
│ │ └── env.ts # Environment configuration
│ ├── middleware/ # Auth, RBAC, validation, rate limiting
│ ├── modules/ # Feature modules
│ ├── services/ # Shared services
│ ├── types/ # TypeScript definitions
│ └── utils/ # Helper utilities
├── prisma/
│ ├── schema.prisma # Main database schema (30+ models)
│ └── migrations/ # Database migrations
└── drizzle/ # Media API schema
```
## Related Documentation
- [Architecture Overview](../architecture/index.md)
- [Database Schema](../database/index.md)
- [API Reference](../api-reference/index.md)
- [Development Guide](../development/index.md)
## Quick Links
- [Authentication Module](modules/auth.md)
- [Campaign Management](modules/campaigns.md)
- [Location Services](modules/locations.md)
- [Media Management](modules/media.md)
- [Email Service](services/email.md)
- [Geocoding Service](services/geocoding.md)

Some files were not shown because too many files have changed in this diff Show More