WyR/project-context.md

1033 lines
15 KiB
Markdown

# Would You Rather Website - Project Context
## Project Goal
Build a lightweight "Would You Rather" website hosted on a Raspberry Pi 5.
Users visit the website, answer a daily question, and immediately see aggregated results after voting.
Example:
> Would you rather be blind or deaf?
* Blind
* Deaf
After voting:
* Blind: 62%
* Deaf: 38%
Total votes: 523
---
# Current Development Strategy
Development is being done on a laptop first.
Reasons:
* Easier coding workflow
* Faster testing
* Git integration from day one
* Easier version control
* Safer than developing directly on the Raspberry Pi
Deployment strategy:
```text
Laptop
Git
GitHub
Raspberry Pi
git pull
Production
```
---
# Technology Stack
## Backend
* Python
* Flask
* Flask application factory pattern
* Flask blueprints for public and admin routes
## Database
* SQLite
* Local development database path: `instance/questions.db`
## Frontend
* HTML
* CSS
* Jinja2 templates
## Authentication
* Flask sessions
* Werkzeug password hashing for admin passwords
## Version Control
* Git
* GitHub
## Future Production
* Gunicorn
* Systemd
* Nginx
* HTTPS with Let's Encrypt
---
# Current Local Project Structure
The project has been refactored out of a single-file Flask app.
Current structure:
```text
would-you-rather/
├── .venv/
├── .gitignore
├── app.py
├── run.py
├── requirements.txt
├── project-context.md
├── app/
│ ├── __init__.py
│ ├── admin.py
│ ├── database.py
│ ├── routes.py
│ │
│ ├── templates/
│ │ ├── public/
│ │ │ ├── index.html
│ │ │ └── results.html
│ │ │
│ │ └── admin/
│ │ ├── dashboard.html
│ │ ├── delete_confirm.html
│ │ ├── login.html
│ │ └── question_form.html
│ │
│ └── static/
│ └── style.css
├── scripts/
│ └── init_db.py
├── tests/
│ └── test_app.py
└── instance/
└── questions.db
```
Notes:
* `app.py` remains as a compatibility entrypoint.
* `run.py` is the preferred local development entrypoint.
* `instance/` is ignored by Git.
* Root-level `questions.db` is ignored and is no longer the active app database.
---
# Current Application Architecture
## App Factory
File:
```text
app/__init__.py
```
Purpose:
* Creates the Flask app with `create_app()`.
* Configures `SECRET_KEY`.
* Configures SQLite database path.
* Ensures the `instance/` directory exists.
* Registers public and admin blueprints.
* Registers database teardown behavior.
Important config:
```python
SECRET_KEY = os.environ.get("WYR_SECRET_KEY", "dev-only-change-me")
DATABASE = instance/questions.db
```
Production must set a real `WYR_SECRET_KEY`.
---
## Database Helpers
File:
```text
app/database.py
```
Purpose:
* Opens SQLite connections with `get_db()`.
* Uses `sqlite3.Row` so routes/templates can access fields by name.
* Closes request-scoped connections with `close_db()`.
* Creates all tables with `init_db()`.
---
## Public Routes
File:
```text
app/routes.py
```
Routes:
```text
/
/vote
/results
```
Behavior:
* `/` displays the question scheduled for today's date.
* `/vote` accepts POSTed `qid` and `vote`.
* `/vote` only accepts votes for today's scheduled question.
* `/results` displays today's vote counts, percentages, and total votes.
* Empty states are shown if no question exists for today.
* Duplicate voting is still blocked by hashing the request IP address.
Current duplicate prevention is temporary.
Future plan:
* Use cookies or session identifiers instead of IP hashing.
---
# Current Database Design
## Questions Table
```sql
CREATE TABLE questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question TEXT NOT NULL,
option_a TEXT NOT NULL,
option_b TEXT NOT NULL,
date TEXT NOT NULL UNIQUE
);
```
Purpose:
Stores scheduled daily questions.
---
## Votes Table
```sql
CREATE TABLE votes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER,
vote TEXT NOT NULL,
voter_hash TEXT NOT NULL
);
```
Purpose:
Stores user votes.
Stored values:
* `question_id`
* selected vote, currently `A` or `B`
* `voter_hash`
---
## Admins Table
```sql
CREATE TABLE admins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
```
Purpose:
Stores admin login accounts.
Passwords must never be stored in plain text.
Current implementation uses:
```python
from werkzeug.security import generate_password_hash, check_password_hash
```
---
# Admin Dashboard V1
Admin functionality has been implemented.
File:
```text
app/admin.py
```
Routes:
```text
/admin/login
/admin/logout
/admin/account
/admin/dashboard
/admin/question/new
/admin/question/edit/<id>
/admin/question/delete/<id>
```
Implemented behavior:
* Admin login with username/password.
* Password verification with Werkzeug password hashing.
* Successful login stores `session["admin"] = True`.
* Protected admin pages use `@admin_required`.
* Unauthenticated users are redirected to `/admin/login`.
* Admins can change their username and password from `/admin/account`.
* Changing credentials requires the current password.
* Dashboard lists all questions ordered by date descending.
* Admins can create questions.
* Admins can edit questions.
* Admins can delete questions.
* Delete also removes votes for that question.
* Duplicate question dates are blocked.
* Required fields are validated.
Dashboard V1 is intentionally simple.
No JavaScript calendar is implemented yet.
---
# Local Development Setup
## Install Dependencies
Use the virtual environment:
```bash
source .venv/bin/activate
pip install -r requirements.txt
```
Or run commands directly through `.venv/bin/python`.
---
## Initialize the Database
Run:
```bash
.venv/bin/python scripts/init_db.py
```
This creates:
```text
instance/questions.db
```
It also seeds:
* one sample question
* one local admin account
Default local admin login:
```text
username: admin
password: admin
```
To override the seeded admin account:
```bash
WYR_ADMIN_USERNAME=myname WYR_ADMIN_PASSWORD=mypassword .venv/bin/python scripts/init_db.py
```
If the admin user already exists, the script will not overwrite that password.
---
## Run the Site Locally
Preferred command:
```bash
.venv/bin/python run.py
```
Then open:
```text
http://127.0.0.1:5000
```
Admin login:
```text
http://127.0.0.1:5000/admin/login
```
Default local login:
```text
admin / admin
```
---
## Restart After Local Changes
If the app is running manually in a terminal:
1. Stop it with `Ctrl+C`.
2. Make changes or pull changes from Git.
3. Restart the app.
Local restart command:
```bash
.venv/bin/python run.py
```
If dependencies changed:
```bash
.venv/bin/pip install -r requirements.txt
```
If database tables changed:
```bash
.venv/bin/python scripts/init_db.py
```
Typical local update flow:
```bash
git pull
.venv/bin/pip install -r requirements.txt
.venv/bin/python scripts/init_db.py
.venv/bin/python run.py
```
---
## Run Tests
Run:
```bash
.venv/bin/python -m unittest discover -s tests
```
Current automated tests cover:
* public empty state
* voting flow
* duplicate vote prevention
* admin login success
* admin login failure
* admin credential changes
* protected dashboard redirect
* duplicate date validation when creating questions
---
# Manual Testing Checklist
## Public Site
1. Start the app:
```bash
.venv/bin/python run.py
```
2. Open:
```text
http://127.0.0.1:5000
```
3. Confirm the sample question appears if it is scheduled for today's date.
4. Click one answer.
5. Confirm the site redirects to `/results`.
6. Confirm results show percentages and total votes.
7. Vote again from the same browser/IP.
8. Confirm the duplicate vote does not increase the vote count.
---
## Admin Site
1. Open:
```text
http://127.0.0.1:5000/admin/login
```
2. Log in with:
```text
admin / admin
```
3. Confirm the dashboard appears.
4. Click `Change Credentials`.
5. Confirm changing credentials requires the current password.
6. Save new credentials, log out, and confirm the new login works.
7. Click `Create Question`.
8. Add today's date or a future date, question, option A, and option B.
9. Save and confirm the new question appears in the dashboard.
10. Edit the question and confirm changes persist.
11. Try creating another question with the same date.
12. Confirm this error appears:
```text
Date already has a question.
```
13. Delete a question and confirm it disappears from the dashboard.
14. Log out.
15. Visit `/admin/dashboard` while logged out and confirm it redirects to `/admin/login`.
---
# Development Notes
## Current Compatibility Entrypoints
Preferred local command:
```bash
.venv/bin/python run.py
```
Compatibility command:
```bash
.venv/bin/python app.py
```
Both create the app through:
```python
from app import create_app
```
---
## Git Ignore Rules
The following local-only files/directories are ignored:
```text
.venv/
__pycache__/
*.pyc
questions.db
instance/
.env
.vscode/
.idea/
```
The active SQLite database is intentionally not committed.
---
## What To Commit To Git
Safe and expected to commit:
```text
app/
scripts/
tests/
app.py
run.py
requirements.txt
.gitignore
project-context.md
```
Also commit deletions/moves when files are refactored.
Current old paths that were replaced:
```text
init_db.py
templates/
static/
```
These old paths should remain deleted once the new `app/` package layout is committed.
---
## What Not To Commit
Keep these local only:
```text
.venv/
instance/
instance/questions.db
questions.db
.env
__pycache__/
.vscode/
.idea/
```
Reasons:
* `.venv/` is machine-specific.
* SQLite databases contain local runtime data.
* `.env` may contain secrets.
* cache/editor files are not project source.
Even with a self-hosted private Git server, avoid committing secrets.
---
# Future Admin Features
## Dashboard Version 2
Use a proper calendar interface.
Possible library:
* FullCalendar
Desired behavior:
```text
June 2026
13 Question
14 Question
15 Empty
16 Question
```
Clicking a date:
* opens edit view if the date has a question
* opens create view if the date is empty
---
## Search
Search questions by:
* date
* question text
---
## Statistics View
Admin dashboard should eventually show:
```text
Question
Blind: 142
Deaf: 98
Total: 240
```
Useful for engagement analytics.
---
# Future Public Features
## Better Duplicate Vote Prevention
Replace IP hashing with:
* cookies
* sessions
* or lightweight anonymous visitor IDs
---
## Archive
Browse historical questions.
---
## Comments
Discussion under questions.
---
## Country Statistics
Example:
```text
Denmark
Blind: 65%
Deaf: 35%
United States
Blind: 52%
Deaf: 48%
```
---
## AI Generated Images
One image per question.
Example:
```text
Would you rather live underwater or in space?
```
with generated artwork.
---
## User Accounts
Optional.
Users can:
* save history
* track participation
---
# Future AI Features
Not a priority yet.
Planned later.
Possible route:
```text
Generate next 30 questions
```
Workflow:
1. AI generates questions.
2. Questions fill empty dates.
3. Admin reviews.
4. Admin approves.
Potential providers:
* OpenAI
* OpenRouter
* Mistral
---
# Future Raspberry Pi Deployment
Local Raspberry Pi testing is currently done manually on the home network.
The future goal is to run the app as a managed service with `systemd`, then later put it behind Gunicorn/Nginx for production.
---
## Raspberry Pi Local Network Test Setup
Install system dependencies:
```bash
sudo apt update
sudo apt install -y git python3 python3-venv
```
Clone the repo:
```bash
git clone <your-self-hosted-git-url> wyr
cd wyr
```
Create and install into a virtual environment:
```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```
Set local secrets and initialize the database:
```bash
export WYR_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
export WYR_ADMIN_USERNAME="youradmin"
export WYR_ADMIN_PASSWORD="yourstrongpassword"
.venv/bin/python scripts/init_db.py
```
Run on the home LAN:
```bash
WYR_SECRET_KEY="$WYR_SECRET_KEY" .venv/bin/flask --app run run --host 0.0.0.0 --port 5000
```
Open from another device on the same network:
```text
http://<raspberry-pi-ip>:5000
```
Admin:
```text
http://<raspberry-pi-ip>:5000/admin/login
```
Do not port-forward this app to the public internet yet.
---
## Restart On The Pi During Manual Testing
If running manually in a terminal:
1. Press `Ctrl+C`.
2. Pull changes.
3. Install any dependency updates.
4. Run database setup if needed.
5. Start the app again.
Command sequence:
```bash
git pull
.venv/bin/pip install -r requirements.txt
.venv/bin/python scripts/init_db.py
WYR_SECRET_KEY="$WYR_SECRET_KEY" .venv/bin/flask --app run run --host 0.0.0.0 --port 5000
```
If the Pi reboots, manually `cd` back into the project and run the Flask command again.
This will become easier after adding `systemd`.
---
## Future Systemd Service
Future goal:
```bash
sudo systemctl start wyr
sudo systemctl stop wyr
sudo systemctl restart wyr
sudo systemctl status wyr
```
Benefits:
* app can start automatically after Pi reboot
* easier restart command
* logs can be viewed with `journalctl`
* app can run in the background without an open terminal
Example future log command:
```bash
journalctl -u wyr -f
```
This is not implemented yet.
---
Planned deployment stack:
```text
Internet
Cloudflare
Nginx
Gunicorn
Flask
SQLite
```
---
# Deployment Workflow
On laptop:
```bash
git add .
git commit -m "feature"
git push
```
On Pi:
```bash
git pull
.venv/bin/pip install -r requirements.txt
.venv/bin/python scripts/init_db.py
```
Then manually restart the Flask command.
Later, after the `systemd` service exists, the Pi restart step will become:
```bash
sudo systemctl restart wyr
```
---
# Immediate Roadmap
## Current Status
Local refactor and Admin Dashboard V1 are implemented.
---
## Next Step
Add cooler layout and styling:
* improve the public voting page design
* improve the results page design
* improve admin dashboard styling
* make buttons, forms, and navigation feel more polished
* add better spacing, typography, and responsive layout
---
## After Styling
Polish the admin flow and public behavior:
* improve layout
* add clearer success messages
* add better vote duplicate handling
* improve the empty state when no question is scheduled for today
* add CSRF protection before public internet exposure
---
## After Local Pi Testing
Implement:
* `systemd` service for easier Pi management
* automatic start on reboot
* clearer production environment variables
* basic log inspection workflow with `journalctl`
---
## After Admin Polish
Implement:
* calendar view
* question scheduling by date
* admin statistics
---
## After Calendar
Implement:
* AI-assisted question generation
---
# Long-Term Goal
A polished daily "Would You Rather" website that:
* runs on a Raspberry Pi 5
* uses a custom domain
* supports HTTPS
* allows browser-based question management
* supports AI-assisted content generation
* scales beyond the initial MVP