Technical Documentation
Custom CMS & Admin Panel
A bespoke full-stack content management system built from scratch — public PHP site, local Python admin backend, dual-mode storage
Origin
This system was built for a non-profit operating in a hostile political environment. I was genuinely worried about retaliation, hacking, and exposure — so the architecture started from a question: what's the safest possible publishing setup for an organization that can't afford to be compromised? The answer was local-first. If the admin never runs in the cloud, there's nothing to breach remotely. Every other decision followed from that constraint.
Overview
This is a purpose-built content management system designed around a specific operational constraint: a single operator managing a public-facing website entirely from a local machine, with no cloud CMS, no third-party admin interface, and no unnecessary dependencies. Every part of the system — from the storage layer to the publish workflow — was designed to serve that constraint precisely.
The public site is intentionally lightweight: PHP, JSON, and vanilla JavaScript. The local admin panel is a sophisticated browser UI backed by a Python standard-library HTTP server with modular handlers, a dual-mode SQLite/JSON storage adapter, and an extensive suite of utility scripts covering post ingestion, media backup, QR tracking, access log analytics, resource management, and publishing.
The architecture was shaped by a deliberate choice to avoid external dependencies wherever possible. The Python backend uses only the standard library. The public site consumes static JSON files. The admin panel runs locally and publishes to a remote PHP host — a workflow that influenced every design decision in the system.
The codebase includes Python unit tests via pytest, frontend tests via Jest, and documentation covering current architecture, admin workflows, operations and maintenance, and a full configuration reference.
Project Map
site/ Public website site/posts.json Published posts data site/action/action-items.json Published action artwork and download data site/resources/resources.json Published resources sections and cards site/wikipedia-tracker/ Public Wikipedia tracker config, cache, PHP API admin-panel/ Local admin UI admin-panel/index.html Admin shell and tab markup admin-panel/_resources/js/ Admin ES modules and Jest tests admin-panel/_resources/python/ Python backend, scripts, handlers, tests admin-panel/_resources/data/ SQLite database and QR history data _commands/ macOS command launchers, test helpers, migrations _config/ Templates and local configuration examples _assets/docs/ Current docs and historical plans and reports _production_logs/ Local synced access log input
Storage Model
The backend uses a dual-mode storage adapter that supports both SQLite and JSON, selectable via
configuration. Active admin operations prefer SQLite; the public site consumes JSON files under
site/
. Handlers that mutate SQLite state sync back to JSON as part of the publish workflow.
-
db_adapter.pyis the public data access layer for all handlers, abstracting the storage mode from handler logic -
config.jsoncontrols the active storage mode via"migration": { "use_database": true } - Legacy JSON mode remains available for migration, parity testing, and fallback
-
SQLite schema migrations run automatically via
db_layer.pywhen the database is opened — no manual migration steps required
Admin Panel Capabilities
Posts
- Add, edit, batch edit, search, sort, select, archive, restore, delete
- Duplicate checking across active, archived, and deleted records
- Link checking, media backup, thumbnail sync
- Upload-date backfill and duration extraction workflow
Post ingestion pipeline
The ingestion pipeline handles the full lifecycle of bringing a new post into the system. Each step is automated where possible and logged where not:
- URL normalization and tracking-parameter removal
- Platform and media-type detection
- Duplicate checking across active, archived, and deleted posts
- Upload date and duration extraction
- Thumbnail extraction and generation
- Article archiving and article thumbnail generation
- Persistence to both JSON and SQLite
Action items
- Category and artwork item management
- PDF and image uploads with print-size detection and preview generation
- Download metadata, variants, archive and restore
- QR code assignment per item
QR system
- Structured QR ID generation and QR image generation UI
- Server-side QR history with link and unlink state
- Scan-log sync, analytics endpoints, and redirect handling
Resources
- Sections and resource cards
- Logo upload, favicon and logo fetching, direct logo download
- Social link fields per resource
Access log analytics
- SFTP sync of Apache logs from the production server
- Apache log parsing with persistent parse-error tracking
- SQLite storage with grouped visitor views, filters, stats, and charts
- Geolocation cache for visitor IP resolution
Wikipedia tracker
- Admin article configuration with reorder, update, and delete
- Cache status and cache-clear controls
- Public stale-while-revalidate API for the live site
Publishing and export
- Sync current SQLite-backed data to public JSON files
- Create export packages for deployment
Tech Stack
| Public site | PHP, JSON, vanilla JavaScript |
| Admin UI | HTML, CSS, ES modules (vanilla JS) |
| Admin backend | Python 3, standard library HTTP server |
| Storage | SQLite (primary), JSON (legacy / public consumption) |
| Frontend tests | Jest |
| Backend tests | pytest |
| Platform | macOS (local admin), Apache (production host) |
Backend Architecture
The Python backend is built entirely on the standard library — no Flask, no Django, no third-party web framework. The HTTP server dispatches requests to modular handlers, each responsible for a discrete feature area. This architecture makes it straightforward to add, test, or remove handlers independently.
Starting the backend
cd admin-panel/_resources/python ./setup_venv.sh python3 simple_server.py
The configured admin start port is 5500, with up to 10 attempts for the next free port.
Starting the public site
open "_commands/ports_and_servers/servers/launch_PHP.command"
Combined launcher
open "_commands/CLEAN+LAUNCH.command"
Running tests
# Python backend tests cd admin-panel/_resources/python python3 -m pytest # Frontend tests cd admin-panel/_resources/js npm test
Documentation Structure
The project maintains a layered documentation system covering current state, historical decisions, and operational procedures:
-
_assets/docs/README.md— documentation index and status of every Markdown file -
CURRENT_SYSTEM_REFERENCE.md— current feature and architecture reference -
ADMIN_PANEL_WORKFLOWS.md— detailed admin UI and feature workflows -
OPERATIONS_AND_MAINTENANCE.md— testing, deployment, backup, link-checking, and maintenance procedures -
admin-panel/_resources/python/README.md— Python backend reference -
CONFIG_REFERENCE.md— full configuration reference -
README-DURATION.md— duration extraction feature notes -
_commands/_commands_analysis.md— command launcher inventory -
_assets/docs/history/— completed code-review records, audits, migration notes, and superseded implementation summaries
Security Baseline
The admin panel is designed to run locally or behind trusted network controls. A code review identified the
absence of active admin authentication in
simple_server.py
. The following are documented as required before any public or network exposure:
- Admin authentication layer
- CSRF protection for state-changing browser workflows
- Stricter SSRF and download limits
None of these represent an architectural constraint — they are known gaps, deliberately deferred given the local-only operational model, and clearly documented for any future deployment that changes that assumption.