796 lines
11 KiB
Markdown
796 lines
11 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
|
|
```
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
---
|
|
|
|
# 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
|
|
|
|
Not currently being worked on.
|
|
|
|
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
|
|
sudo systemctl restart wyr
|
|
```
|
|
|
|
---
|
|
|
|
# Immediate Roadmap
|
|
|
|
## Current Status
|
|
|
|
Local refactor and Admin Dashboard V1 are implemented.
|
|
|
|
---
|
|
|
|
## Next Step
|
|
|
|
Polish the admin flow and public UI:
|
|
|
|
* improve layout
|
|
* add clearer success messages
|
|
* add better vote duplicate handling
|
|
* improve the empty state when no question is scheduled for today
|
|
|
|
---
|
|
|
|
## 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
|