Jul 22 - Initial code
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# 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='<p>New copy with <strong>bold</strong>.</p>'
|
||||
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','<p>Content.</p>','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=<paste hash from command below>
|
||||
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.
|
||||
Reference in New Issue
Block a user