Compare commits

..

No commits in common. "3de1d3fca547af86ff1b2ff60de20266c40aa269" and "eb16815f911884b5db5d0cf3d77ea6c6344366e5" have entirely different histories.

3 changed files with 62 additions and 156 deletions

186
README.md
View File

@ -1,156 +1,84 @@
<p align="center"> # Changemaker Lite
<img src="mkdocs/docs/assets/logo.png" alt="Changemaker Lite" width="120" />
</p>
<h1 align="center">Changemaker Lite</h1> A self-hosted political campaign platform that consolidates advocacy email campaigns, geographic mapping, volunteer canvassing, media management, and administration into a single TypeScript stack. Built for organizers who want to own their data.
<p align="center"> ## What Is This?
A self-hosted campaign platform for community organizers who want to own their data.
</p>
<p align="center"> Changemaker Lite gives community organizers the tools they need to:
<a href="https://cmlite.org/docs/getting-started/">Documentation</a> &middot;
<a href="https://cmlite.org">Website</a> &middot;
<a href="https://opensource.org/license/apache-2-0">Apache 2.0 License</a>
</p>
--- - **Run advocacy campaigns** — let supporters look up their elected representatives by postal code and send emails in a few clicks
- **Manage canvassing** — map locations, draw canvassing areas, schedule volunteer shifts, and track door-to-door visits with GPS
- **Host media** — upload campaign videos, share them publicly, and track engagement analytics
- **Build landing pages** — drag-and-drop page builder for campaign microsites
- **Send newsletters** — integrated with Listmonk for opt-in mailing lists
- **Monitor everything** — Prometheus + Grafana observability stack included
Changemaker Lite consolidates advocacy campaigns, geographic mapping, volunteer canvassing, media management, newsletters, and administration into a single Docker Compose stack. One `.env` file, one command to start, everything under your control. The entire platform runs on Docker Compose with a single `.env` file for configuration.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/admin-dashboard.png" alt="Admin Dashboard" width="800" />
</p>
## Why Changemaker Lite?
Most campaign tools are SaaS platforms that lock you into monthly subscriptions, hold your data hostage, and disappear when funding dries up. Changemaker Lite is different:
- **Self-hosted** -- runs on any machine with Docker. Your server, your data.
- **All-in-one** -- replaces 5-10 separate tools with a single integrated platform.
- **Free and open source** -- Apache 2.0 licensed. Fork it, modify it, make it yours.
- **Privacy-first** -- no telemetry, no third-party analytics, no data leaving your server.
## What's Inside
### Advocacy Campaigns
Let supporters look up their elected representatives by postal code and send advocacy emails in a few clicks. Track responses, moderate a public response wall, and monitor email delivery.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/public-campaigns.png" alt="Public Campaign Page" width="800" />
</p>
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/influence-campaigns.png" alt="Campaign Management" width="800" />
</p>
### Interactive Map & Canvassing
Import thousands of addresses, draw canvassing areas, schedule volunteer shifts, and track door-to-door visits with GPS. Volunteers get a full-screen mobile map with real-time location tracking and visit recording.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/public-map.png" alt="Public Map" width="800" />
</p>
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/canvass-dashboard.png" alt="Canvass Dashboard" width="800" />
</p>
### Volunteer Portal
Volunteers get their own portal with shift sign-ups, canvassing assignments, activity tracking, a social calendar, and a friends system to stay connected with their team.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/volunteer-dashboard.png" alt="Volunteer Map" width="800" />
</p>
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/volunteer-calendar.png" alt="Volunteer Calendar" width="800" />
</p>
### Media Library & Public Gallery
Upload campaign videos, manage metadata, schedule publishing, and share them through a public gallery. Includes GDPR-compliant analytics.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/media-library.png" alt="Media Library" width="800" />
</p>
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/public-gallery.png" alt="Public Gallery" width="800" />
</p>
### Landing Pages & Email Templates
Build campaign microsites with a drag-and-drop GrapesJS editor. Design email templates for consistent campaign communications.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/landing-pages.png" alt="Landing Page Builder" width="800" />
</p>
### SMS Campaigns, Newsletters & More
Send SMS campaigns via an Android bridge, sync subscribers to Listmonk for newsletters, recognize volunteers on a Wall of Fame leaderboard, and monitor everything with built-in Prometheus + Grafana observability.
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/sms-dashboard.png" alt="SMS Dashboard" width="800" />
</p>
<p align="center">
<img src="mkdocs/docs/assets/images/screenshots/features/public-wall-of-fame.png" alt="Wall of Fame" width="800" />
</p>
## Quick Start ## Quick Start
```bash ```bash
# One-command install (downloads pre-built images, runs config wizard) # Clone and switch to the v2 branch
curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh | bash
cd ~/changemaker.lite
docker compose up -d
```
Or clone and build from source:
```bash
git clone <repo-url> changemaker.lite git clone <repo-url> changemaker.lite
cd changemaker.lite && git checkout v2 cd changemaker.lite
git checkout v2
# Create your environment file
cp .env.example .env cp .env.example .env
# Edit .env -- set passwords, JWT secrets, admin credentials # Edit .env — at minimum set:
# V2_POSTGRES_PASSWORD, REDIS_PASSWORD,
# JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, ENCRYPTION_KEY
# INITIAL_ADMIN_EMAIL, INITIAL_ADMIN_PASSWORD
# Start core services
docker compose up -d v2-postgres redis api admin docker compose up -d v2-postgres redis api admin
# Run database migrations and seed
docker compose exec api npx prisma migrate deploy docker compose exec api npx prisma migrate deploy
docker compose exec api npx prisma db seed docker compose exec api npx prisma db seed
``` ```
Then open **http://localhost:3000** and log in with the admin credentials from your `.env`. Then open **http://localhost:3000** and log in with the admin credentials from your `.env`.
## Architecture
| Component | Technology | Port |
|-----------|-----------|------|
| **API** | Express.js + Prisma + PostgreSQL | 4000 |
| **Media API** | Fastify + Prisma (shared DB) | 4100 |
| **Admin GUI** | React + Vite + Ant Design + Zustand | 3000 |
| **Reverse Proxy** | Nginx (subdomain routing) | 80 |
| **Database** | PostgreSQL 16 | 5433 |
| **Cache / Queue** | Redis + BullMQ | 6379 |
| **Newsletter** | Listmonk | 9001 |
| **Monitoring** | Prometheus + Grafana + Alertmanager | 9090, 3001 |
See `CLAUDE.md` for comprehensive architecture documentation, module reference, and troubleshooting.
## Feature Flags
Enable optional modules in `.env`:
```bash
ENABLE_MEDIA_FEATURES=true # Video library + gallery
LISTMONK_SYNC_ENABLED=true # Newsletter subscriber sync
EMAIL_TEST_MODE=true # Route emails to MailHog (dev)
```
## Production Deployment
Changemaker Lite uses [Pangolin](https://github.com/fosrl/pangolin) tunnels for production access (Cloudflare alternative). See the Tunnel page in the admin panel (`/app/tunnel`) for setup instructions.
## Documentation ## Documentation
**Full documentation is available at [cmlite.org/docs/getting-started](https://cmlite.org/docs/getting-started/).** - **`CLAUDE.md`** — Full project reference (architecture, modules, ports, patterns)
- **`V2_PLAN.md`** — Development roadmap (Phases 1-14 complete)
- **`SECURITY_AUDIT_2025-02-11.md`** — Security audit findings and remediations
- **`.env.example`** — All 100+ environment variables with descriptions
The docs site covers installation, configuration, all features, architecture details, production deployment with Pangolin tunnels, and troubleshooting. It is the authoritative and up-to-date reference for Changemaker Lite. ## Licensing
## Architecture at a Glance This project is licensed under the [Apache License 2.0](https://opensource.org/license/apache-2-0).
| Layer | Technology |
|-------|-----------|
| API | Express.js + Prisma + PostgreSQL 16 |
| Media API | Fastify + Prisma (shared DB) |
| Frontend | React + Vite + Ant Design + Zustand |
| Reverse Proxy | Nginx (subdomain routing) |
| Cache & Queue | Redis + BullMQ |
| Newsletter | Listmonk |
| Monitoring | Prometheus + Grafana + Alertmanager |
| Tunneling | Pangolin (self-hosted Cloudflare alternative) |
The entire stack runs on Docker Compose. Enable optional modules (media, newsletters, SMS, monitoring) with feature flags in `.env`.
## License
[Apache License 2.0](https://opensource.org/license/apache-2-0)
## AI Disclaimer ## AI Disclaimer

View File

@ -938,27 +938,20 @@ export default function DocsPage() {
// Auto-refresh preview when remote changes arrive in collab mode // Auto-refresh preview when remote changes arrive in collab mode
useEffect(() => { useEffect(() => {
if (!collab.active || !collab.yText || !selectedFile) return; if (!collab.active || !collab.yText) return;
let refreshTimer: ReturnType<typeof setTimeout>; let refreshTimer: ReturnType<typeof setTimeout>;
const observer = () => { const observer = () => {
clearTimeout(refreshTimer); clearTimeout(refreshTimer);
refreshTimer = setTimeout(() => { refreshTimer = setTimeout(() => {
// Re-set src to force reload (avoids cross-origin issues with contentWindow.reload) previewIframeRef.current?.contentWindow?.location.reload();
if (previewIframeRef.current && selectedFile) { }, 2000);
const url = filePathToMkDocsUrl(selectedFile);
// Append cache-buster to force fresh load
const buster = `_t=${Date.now()}`;
const sep = url.includes('?') ? '&' : '?';
previewIframeRef.current.src = url + sep + buster;
}
}, 2500);
}; };
collab.yText.observe(observer); collab.yText.observe(observer);
return () => { return () => {
collab.yText?.unobserve(observer); collab.yText?.unobserve(observer);
clearTimeout(refreshTimer); clearTimeout(refreshTimer);
}; };
}, [collab.active, collab.yText, selectedFile]); }, [collab.active, collab.yText]);
const handleToolbarSnippet = useCallback((snippetId: string) => { const handleToolbarSnippet = useCallback((snippetId: string) => {
if (snippetId === 'video-card') { if (snippetId === 'video-card') {

View File

@ -257,23 +257,8 @@ const docsExtension: Extension = {
return; return;
} }
// Duplication guard: detect and fix content that appears to be doubled
const content = yText.toString();
if (content.length > 20) {
const half = Math.floor(content.length / 2);
const firstHalf = content.substring(0, half);
const secondHalf = content.substring(half);
if (firstHalf === secondHalf) {
logger.warn(`Docs collab: detected duplicated content in ${documentName} (${content.length} chars), fixing`);
// Replace the Y.Text with just the first half
document.transact(() => {
yText.delete(half, content.length - half);
});
return; // The deletion triggers another onChange with the fixed content
}
}
// Write plaintext to disk (debounced by Hocuspocus's built-in debounce) // Write plaintext to disk (debounced by Hocuspocus's built-in debounce)
const content = yText.toString();
try { try {
await docsFilesService.writeFileContent(documentName, content); await docsFilesService.writeFileContent(documentName, content);
// Invalidate Redis file cache // Invalidate Redis file cache