# JQC Features Site Single-page, modern site for LT Services' JQC program. Numbered sections; each topic shows title only until clicked, then expands to reveal text, links, photos, or videos. Stack: **Flask + MySQL + Gunicorn + systemd + Nginx** on Ubuntu 24.04. ``` app.py Flask app + Section/Topic models + routes config.py env-driven config (DB + demo contact URL) schema.sql MySQL DDL + seed content (safe to re-run) templates/index.html static/css/style.css static/js/main.js accordion expand/collapse gunicorn.conf.py deploy/jqc-features.service deploy/nginx.conf .env.example ``` --- ## Deploy ### 1. Database (run once) ```bash sudo mysql < schema.sql sudo mysql -e "CREATE USER IF NOT EXISTS 'jqc_features'@'127.0.0.1' IDENTIFIED BY 'CHANGE_ME'; GRANT ALL PRIVILEGES ON jqc_features.* TO 'jqc_features'@'127.0.0.1'; FLUSH PRIVILEGES;" ``` `schema.sql` is idempotent — re-running updates seed content in place, never duplicates rows. ### 2. Code ```bash sudo useradd -r -s /usr/sbin/nologin jqcweb # service account sudo mkdir -p /opt/jqc-features sudo rsync -a ./ /opt/jqc-features/ # copy project here cd /opt/jqc-features sudo python3 -m venv venv sudo ./venv/bin/pip install -r requirements.txt sudo cp .env.example .env # then edit .env with the real DB_PASSWORD sudo chown -R jqcweb:jqcweb /opt/jqc-features ``` ### 3. Service (systemd — separate from DB step) ```bash sudo cp deploy/jqc-features.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now jqc-features sudo systemctl status jqc-features # confirm active (running) curl -s http://127.0.0.1:8000/healthz # -> {"status":"ok"} ``` ### 4. Nginx ```bash # edit server_name in deploy/nginx.conf first sudo cp deploy/nginx.conf /etc/nginx/sites-available/jqc-features sudo ln -s /etc/nginx/sites-available/jqc-features /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx # TLS: sudo certbot --nginx -d features.ltservicesinc.com ``` --- ## Adding photos, videos, or links to a topic Content lives in the `topic` table. No code change needed — update rows, then the page picks them up on next load. Text only (default): ```sql UPDATE topic SET body_html='

New copy with bold.

' WHERE slug='comprehensive-checklists'; ``` Add a photo: ```sql UPDATE topic SET media_type='image', media_url='/static/img/checklist.jpg', media_caption='Timestamped inspection photo' WHERE slug='comprehensive-checklists'; ``` (Put the file in `static/img/` — create the folder — or use any absolute URL, e.g. a Cloudflare R2 link.) Add an uploaded video: ```sql UPDATE topic SET media_type='video', media_url='/static/vid/demo.mp4' WHERE slug='real-time-issue-tracking'; ``` Embed a YouTube clip: ```sql UPDATE topic SET media_type='embed', media_url='https://www.youtube.com/embed/VIDEO_ID' WHERE slug='notifications-and-qr-access'; ``` Add a button link (works alongside any media): ```sql UPDATE topic SET link_url='https://jqc.ltservicesinc.com', link_label='Open the portal' WHERE slug='shared-inspector-access'; ``` `media_type` values: `none`, `image`, `video`, `embed`. ## Add a whole new section or topic ```sql INSERT INTO section (num,title,subtitle,sort_order) VALUES (5,'New Section','Optional subtitle',50); INSERT INTO topic (section_id,slug,title,body_html,media_type,sort_order) VALUES ((SELECT id FROM section WHERE num=5), 'my-new-topic','My New Topic','

Content.

','none',10); ``` Sections order by `sort_order`; topics order by `sort_order` within a section. --- ## Admin panel (browser editing) Instead of SQL, edit content at **`/admin`**. Single admin account, session login, CSRF-protected forms, and every create/update/delete is written to `audit_log`. ### One-time setup 1. **Already-deployed DB?** add the audit table: ```bash sudo mysql < add_admin.sql ``` (New deploys skip this — `schema.sql` already includes `audit_log`.) 2. Set these in `/opt/jqc-features/.env`: ```bash SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") ADMIN_USERNAME=admin ADMIN_PASSWORD_HASH= SESSION_COOKIE_SECURE=1 # keep 1 in production (HTTPS) ``` Generate the password hash (never store the plaintext): ```bash ./venv/bin/python -c "from werkzeug.security import generate_password_hash as g; print(g('YOUR_PASSWORD'))" ``` 3. Install the new dependency and restart: ```bash sudo ./venv/bin/pip install -r requirements.txt sudo systemctl restart jqc-features ``` Then visit `https://your-domain/admin`, sign in, and manage content. ### What you can do - **Dashboard** — every section with its topics; edit or delete inline. - **Add / edit topic** — section, title, slug (auto if blank), body HTML, media (image / video / embed), caption, an optional link button, and sort order. - **Add / edit section** — number (`§NN`, unique), title, subtitle, sort order. - Deleting a section cascades to its topics (with a confirm prompt). ### Notes - `SESSION_COOKIE_SECURE=1` means the login cookie only sends over HTTPS. For a quick plain-HTTP test on the box, set it to `0` — never in production. - `SECRET_KEY` must be stable and secret; changing it logs everyone out. - The admin routes live under `/admin`; the public page and `schema.sql` SQL workflow above still work unchanged.