Skip to content

pburney/FTS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FTS — Civic Consensus Tracker

License: MIT

Measure public support for any constitutional or civic change.

FTS is an open-source web application for running a public, anonymous consensus tracker. Participants register their support with a single click — no account, no email. A gauge shows progress toward a configurable ratification threshold, with per-region breakdowns. The canonical deployment is jordan92.com, which tracks public support for removing the slavery exception from the 13th Amendment.


What Is This?

The 13th Amendment to the US Constitution, ratified in 1865, abolished slavery — except for one carve-out buried in the text:

"Neither slavery nor involuntary servitude, except as a punishment for crime whereof the party shall have been duly convicted, shall exist within the United States, or any place subject to their jurisdiction."

Those 13 highlighted words still permit forced prison labor today. jordan92.com exists to count how many Americans want them gone.

FTS is the framework powering that site. It can be deployed for any civic issue where you want a clear, growing public count tied to an actual ratification or approval threshold.


How It Works

Participants register anonymously — no account, no email, just an anonymous device ID and an optional state selection. The gauge shows the percentage of estimated US smartphone users who have joined. State-level tracking is central because ratification happens state by state.


Tech Stack

  • Backend: Node.js + Express, better-sqlite3, Helmet, express-rate-limit
  • Frontend: Vanilla JS (ES modules), inline CSS, SVG gauge, PWA-capable
  • Database: SQLite (single file, WAL mode)
  • Identity: UUID stored in localStorage — anonymous, no PII collected

API Endpoints

Method Endpoint Description
POST /api/join Register a participant (device_id, optional state) — rate-limited
GET /api/stats National + per-state counts and percentages
GET /api/status/:device_id Check whether a device has already joined

Local Development

Prerequisites: Node.js 18+

git clone <repo-url>
cd FTS
npm install
cp .env.example .env
cp public/js/site.config.example.js public/js/site.config.js
npm run dev

Open http://localhost:3000.

The dev server uses node --watch so it restarts on file changes. The SQLite database is created automatically at ./fts.db on first run.


Deploying Your Own Instance

FTS uses two config files that are never committed to git — you create them from their examples.

1. Server config (.env)

cp .env.example .env

Edit .env:

PORT=3000
NODE_ENV=production
DB_PATH=/var/data/fts/fts.db   # use a persistent path outside the app directory
SITE_URL=https://your-domain.example.com
TURNSTILE_WORKER_URL=https://your-turnstile-siteverify-worker.workers.dev

SITE_URL is injected into the og:url and og:image meta tags at serve time — set it to your canonical domain so social shares link back to your deployment.

2. Frontend content (public/js/site.config.js)

cp public/js/site.config.example.js public/js/site.config.js

Edit public/js/site.config.js. Every field is documented in the example file. Key fields:

Field Description
siteUrl Your canonical domain — used in the share button
mission.title / .tagline The headline and subhead shown on every page
turnstile.siteKey Your Cloudflare Turnstile site key (see below)
amendment.currentHtml The text being changed, with <mark> around the target clause
amendment.proposedText The text as it would read after the change
ratification.threshold The approval percentage you're counting toward (e.g. 0.75 for 75%)
creator.* Your name, bio, and contact info shown on the About page
about.siteNameSection Explains your domain/site name
about.whyThisSection Motivates the cause

3. Replace icons and OG image

Replace the files in public/icons/ with your own branding:

  • icon-192.png and icon-512.png — PWA icons
  • og.png — social share preview (1200×630px recommended)

4. Update public/manifest.json

Edit the three lines that identify your deployment:

"name": "Your App Name",
"short_name": "YAN",
"description": "Your short description."

5. Issue-specific content page

The public/js/pages/why.js file contains a long-form essay specific to the Jordan92/13th Amendment deployment. Replace it with your own "Why This Matters" page, or remove the route from public/js/app.js if you don't need one.


Production Deployment

Install dependencies

npm install --omit=dev

Start the server

npm start

Process manager (recommended)

Use pm2 or a systemd unit to keep the server alive across reboots.

pm2 example:

npm install -g pm2
pm2 start src/server.js --name fts
pm2 save
pm2 startup

systemd example (/etc/systemd/system/fts.service):

[Unit]
Description=FTS Civic Consensus Tracker
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/fts
EnvironmentFile=/opt/fts/.env
ExecStart=/usr/bin/node src/server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Reverse proxy (recommended)

Run FTS behind nginx, Apache, or Caddy to handle TLS and forward traffic.

nginx snippet:

server {
    listen 443 ssl;
    server_name your-domain.example.com;

    location / {
        proxy_pass http://127.0.0.1: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;
    }
}

Apache VirtualHost snippet (requires mod_proxy, mod_proxy_http, mod_headers):

<VirtualHost *:443>
    ServerName your-domain.example.com

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
    RequestHeader set X-Forwarded-Proto "https"

    SSLEngine On
    SSLCertificateFile    /etc/ssl/certs/your-domain.example.com.pem
    SSLCertificateKeyFile /etc/ssl/private/your-domain.example.com.key
</VirtualHost>

ProxyPreserveHost On passes the original Host header through. RequestHeader set X-Forwarded-Proto is needed because Apache doesn't set it automatically the way nginx does; without it, Express would think all requests are plain HTTP.

Caddy snippet (Caddyfile):

your-domain.example.com {
    reverse_proxy localhost:3000
}

Scaling note

FTS uses a single SQLite file. This is intentional — the data is simple, write volume is low, and SQLite in WAL mode handles concurrent reads gracefully. Horizontal scaling (multiple processes sharing the DB) is not supported without switching to a network database. For a civic petition tracker this is unlikely to be needed.


Environment Variables

Variable Default Description
PORT 3000 HTTP port to listen on
NODE_ENV development Set to production in prod
DB_PATH ./fts.db Path to the SQLite database file
SITE_URL http://localhost:3000 Canonical URL injected into OG meta tags
TURNSTILE_WORKER_URL (none) URL of the deployed Cloudflare Turnstile siteverify Worker (see below). If unset, /api/join skips token verification — fine for local dev, must be set in production.

Turnstile Bot Protection

The join flow is protected by Cloudflare Turnstile, verified server-side via a dedicated Cloudflare Worker (see docs/adr/002-turnstile-bot-protection.md for why a Worker instead of direct siteverify).

Your Turnstile site key goes in public/js/site.config.js under turnstile.siteKey. The site key is public-facing (it's embedded in browser JS), so it doesn't need to be in .env. The secret key is held inside the Cloudflare Worker and never touches this codebase.

Set your Worker's URL as TURNSTILE_WORKER_URL in .env.

About

Open-source civic consensus tracker — anonymous, no account, no email. Counts public support toward a ratification threshold with per-state breakdowns. Deployed at jordan92.com to Fix the 13th Amendment.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors