Architecture & Zero-Database Data Model

Standalone Qwiki separates application logic from live portal state using flat JSON manifests and filesystem directories. No SQL engine, cache daemon, or build step is required.

Directory Layout

standalone-qwiki/
├── index.php               # Front controller, clean URL router & reader UI
├── .htaccess               # Apache clean URL routing & .json/.md/content protection
├── qwiki.json              # Portal settings, extensions, category tree & chapter metadata
├── users.json              # Bcrypt password hashes, roles, 2FA TOTP secrets & personal API keys
├── api/
│   ├── admin.php           # Authenticated UI REST actions & extension handler delegation
│   ├── feed.php            # Per-category RSS/Atom syndication endpoint
│   ├── llm.php             # Key-authenticated LLM/AI Agent retrieval & OpenAPI endpoint
│   ├── publish.php         # Headless publishing, category provisioning & media upload API
│   └── search.php          # Server-side full-text search across all enabled page types
├── assets/
│   ├── css/                # Core design system + cascading themes (theme-*.css)
│   ├── js/                 # Vanilla JS UI modules, soft-lock client & qrcode.min.js
│   └── extensions/         # Self-contained page_type and utility plugins
├── bin/                    # Operator CLI tools (reset-2fa.php, sync-subwiki-docs.php)
├── content/                # Document files (.md, .html, .pdf, .form.json) + .locks/ + .logs/
├── data/                   # Protected governance state (revisions/, trash/, talk/)
├── lib/Core/               # PHP core classes (Config, Auth, Navigation, DocumentService, etc.)
├── templates/modals/       # Server-rendered modal dialogs
└── uploads/                # Uploaded images, videos & remote transclusion cache

Request & Storage Flow

flowchart TD
    Req["HTTP Request (/category/slug or /api/*.php)"] --> Router["index.php or api/*.php"]
    Router --> Auth["Auth & LlmAccess (Session / API Key / Share Key)"]
    Auth --> Config["Config::load() loads qwiki.json & users.json"]
    Config --> Nav["Navigation (Tree, Visibility & Status Gating)"]
    Nav --> DocSvc["DocumentService (SHA-256 Check, Revision Snapshot, Atomic Write)"]
    DocSvc --> Files["content/ & data/ (revisions, trash, locks, logs)"]
    Nav --> ExtMgr["ExtensionManager (assets/extensions/*/manifest.json)"]

Core Data Contracts

  • qwiki.json: Stores global settings (title, logoText, theme, defaultBook, requireLoginToView, twoFactorPolicy, extensions, smtp, llmKeys, publishApiKey, revisions), and the recursive books array containing categories (type: "folder"), hyperlinks (type: "link"), and chapters (slug, docId, title, type, file, status, shareKey, publicShareable, visibility).
  • Immutable docId: Navigation::ensureDocIdForChapter() assigns a persistent docId per chapter so revision history (data/revisions/{docId}/) survives title renames, slug updates, and category moves.
  • Unified DocumentService::replaceContent(): Routes UI edits, file replacements, form/HTML saves, revision restores, and headless api/publish.php overwrites through one pipeline with SHA-256 change detection (skipping duplicate revision snapshots when bytes are unchanged), HTML data-qwiki-status synchronization, and active soft-lock migration on relocation.
  • Protected data/ Isolation: data/revisions/, data/trash/, and data/talk/ carry dual Apache 2.2/2.4 .htaccess deny rules and are blocked from front-controller routing in index.php. Core self-updates preserve data/ while full backups include it.