Files
homeschool/README.md
2026-03-10 22:39:59 -07:00

346 lines
22 KiB
Markdown

# 🏠 Homeschool Dashboard
A self-hosted web app for managing homeschool schedules, tracking daily learning sessions, and logging activities. Features a full-screen TV dashboard with live timers and real-time updates via WebSockets.
---
## Features
- **TV Dashboard** — Full-screen display for the living room TV. Shows the current subject, countdown timer, day progress bar, activity options, and the schedule block list. Updates live without page refresh via WebSocket.
- **Morning Routine** — Define a list of morning routine items in Admin. They appear in the TV dashboard Activities panel during the "Good Morning" greeting before the first block starts, then switch to subject-specific activities once a block begins.
- **Break Time** — Each schedule block can optionally include a break at the end. Enable the checkbox and set a duration (in minutes) when building a block in Admin. Once the block's main timer is done, a **Break Time** section appears on the Dashboard with its own **Start / Pause / Resume / Reset** controls — the break does not start automatically. While break is active the TV left column switches to an amber break badge and countdown timer, and the center column shows the configurable **Break Activities** list instead of subject options. Break timer state is fully restored when navigating away and returning to the dashboard.
- **Break Activities** — A global list of break-time activities (e.g. "Get a snack", "Go outside") managed in Admin → Break Activities, using the same add/edit/delete interface as Morning Routine. These items are shown on the TV during any active break.
- **Day Progress Bar** — Both the TV dashboard and the parent dashboard display a progress bar showing how far through the day the child is. Progress is calculated from total scheduled block time vs. remaining block time — not wall-clock time — so it advances only as blocks are actively worked. On the TV the bar is labeled **Start** and **Finish**. On the parent dashboard the left label shows the scheduled start time of the first block and the right label shows a live-updating **estimated finish time** computed as the current time plus all remaining block time and break time for incomplete blocks.
- **Schedule Builder** — Create named schedule templates with time blocks assigned to subjects. Each block supports an optional custom duration override, label, and break time setting. Managed inside the Admin page.
- **Daily Sessions** — Start a school day against a schedule template. Click any block in the list to select it as the current block. Use the **Start** button to begin timing, **Pause** to stop, **Resume** to continue from where you left off, **Done** to mark it as fully complete, and **Reset** to clear the elapsed time back to zero (timer stays paused). Elapsed time per block is remembered across switches, so returning to a block picks up where it left off.
- **Done Button** — Marks the current block as fully elapsed (timer snaps to 00:00 / "Done!"), adds a checkmark in the schedule list, and logs "Marked Done by User" in the activity log. Pressing Reset after Done clears the completed state entirely — removing the checkmark and the Done label — treating the block as if it were never started.
- **Block Timer Remaining** — Each block in the schedule list shows time remaining (allocated duration minus elapsed), counting down live on both the parent dashboard and the TV sidebar. Shows "< 1 min" when under a minute, and "Done!" when the full duration is elapsed.
- **Tablet-Friendly Controls** — The session control buttons (Start/Pause/Resume, Reset, Done, End Day) stretch full-width across the session card and are taller for easy tapping on a tablet.
- **Activity Log** — Automatically records every timer event (day started, block start/pause/resume/complete/reset, break start/pause/resume/reset) as a timestamped timeline. Block selection events are intentionally not logged. The "Done" button logs as "Marked Done by User — Block Name". All event types display with a human-readable label and icon. Includes which schedule template was used. Supports manual notes with free text. Browse and filter history by child and date.
- **Behavior Tracking (Strikes)** — Issue up to 3 strikes per child from the Dashboard. Strike additions and removals are logged in the activity log with a timestamp. Strike count is shown on the TV dashboard. Strikes **reset automatically at midnight** in the user's configured timezone — the reset happens lazily on the first page load after midnight, so no scheduled job is required.
- **Timezone Support** — Set your local timezone in Admin → Settings. All activity log timestamps display in your timezone, including the TV dashboard clock. Midnight strike resets use this timezone.
- **Password Change** — Users can change their own account password from Admin → Settings → Reset Password. The form requires the current password before accepting a new one.
- **Multi-Child Support** — Manage multiple students under one parent account, each with their own color, schedule, and history.
- **JWT Authentication** — Secure parent login with access tokens and httpOnly refresh cookies. TV dashboard is public (no login required). Disabled accounts receive a clear error message explaining the account is disabled rather than a generic "invalid credentials" response.
- **Super Admin Panel** — A separate admin interface (at `/super-admin`) for site-wide management. Log in with a dedicated admin username and password (set in `.env`). Provides full control over all registered parent accounts:
- **Impersonate** — Enter any user's session to view and manage their data. An impersonation banner is shown at the top of every page with a one-click "Exit to Admin Panel" button.
- **Reset Password** — Set a new password for any user without needing the current password.
- **Disable / Enable** — Disable a user's login access. Disabled users cannot log in and see a specific error message. Re-enable at any time.
- **Delete** — Permanently delete a user and all associated data (children, sessions, schedules, subjects, activity logs, etc.) with a confirmation dialog. This action cannot be undone.
- **Last Active** — Shows the date each user last logged in or refreshed their session, displayed in that user's configured timezone. Shows "Never logged in" for accounts that have never authenticated.
- **Meeting Subject** — A system subject called "Meeting" (calendar icon) is automatically created for every user and cannot be deleted or renamed. Add it to any schedule block like a normal subject and assign activity options (e.g. agenda items) that will display on the TV during the meeting.
- **Meeting Notifications** — When a schedule block assigned to the Meeting subject is approaching, the app automatically alerts everyone:
- **5-minute warning** — An amber corner toast appears on both the parent Dashboard and the TV with the meeting name and a live countdown. Tap X to dismiss.
- **1-minute re-notify** — If the 5-minute toast was dismissed, it reappears at the 1-minute mark.
- **At start time** — A full-screen overlay fires on the TV with the meeting name and a 30-second auto-dismiss countdown (tap anywhere to dismiss early). Simultaneously, the schedule switches to the Meeting block and its timer starts automatically.
- **Chime sounds** — A rising three-note chime plays on warnings; a falling chime plays at meeting start. Generated via the Web Audio API — no audio files required.
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Frontend | Vue 3 + Vite + Pinia + Vue Router |
| Frontend server | nginx (Docker) |
| Backend API | FastAPI (Python 3.12) |
| Real-time | WebSockets via FastAPI |
| Database | MySQL 8 |
| ORM | SQLAlchemy 2.0 (async) |
| Auth | JWT — python-jose + passlib/bcrypt |
| Orchestration | Docker Compose |
---
## Project Structure
```
homeschool/
├── docker-compose.yml # Stack definition (3 services: db, backend, frontend)
├── .env.example # Environment variable template
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
│ ├── main.py # FastAPI app entry point + table auto-creation + migrations
│ ├── config.py # Settings (reads from .env)
│ ├── database.py # Async SQLAlchemy engine
│ ├── dependencies.py # Auth dependencies (get_current_user, get_admin_user)
│ ├── auth/jwt.py # Token creation, password hashing
│ ├── models/ # SQLAlchemy ORM models
│ │ ├── child.py # Child (incl. strikes, strikes_last_reset)
│ │ ├── subject.py # Subject + SubjectOption
│ │ ├── schedule.py # ScheduleTemplate + ScheduleBlock (incl. break fields)
│ │ ├── session.py # DailySession + TimerEvent
│ │ ├── activity.py # ActivityLog (manual notes)
│ │ ├── morning_routine.py# MorningRoutineItem
│ │ ├── break_activity.py # BreakActivityItem
│ │ ├── strike.py # StrikeEvent (strike history)
│ │ └── user.py # User (incl. timezone, last_active_at)
│ ├── schemas/ # Pydantic request/response schemas
│ ├── routers/ # API route handlers
│ │ ├── auth.py # Login, register, refresh, logout, change-password
│ │ ├── children.py # Children CRUD + strikes + midnight reset
│ │ ├── subjects.py
│ │ ├── schedules.py
│ │ ├── sessions.py # Timer actions + break timer events
│ │ ├── logs.py # Timeline + strike events
│ │ ├── morning_routine.py
│ │ ├── break_activity.py # Break activities CRUD
│ │ ├── dashboard.py # Public snapshot endpoint (TV + dashboard reload)
│ │ ├── admin.py # Super admin: login, user list, impersonate, reset-password,
│ │ │ # toggle-active, delete-user
│ │ └── users.py
│ ├── utils/
│ │ └── timer.py # Elapsed-time computation for block and break timers
│ └── websocket/manager.py # WebSocket connection manager
└── frontend/
├── Dockerfile # Multi-stage: Node build → nginx serve
├── nginx.conf # Proxy /api/ and /ws/ to backend
├── index.html # Favicon set to house emoji via inline SVG
└── src/
├── composables/
│ ├── useApi.js # Axios with auto token-refresh
│ ├── useWebSocket.js # Auto-reconnecting WebSocket
│ └── useMeetingAlerts.js # Meeting countdown, corner toasts, TV overlay, chimes
├── stores/ # Pinia: auth, children, schedule, superAdmin
├── views/ # LoginView, TVView, DashboardView, LogView, AdminView,
│ # SuperAdminLoginView, SuperAdminView
└── components/ # TimerDisplay, ScheduleBlock, NavBar, etc.
```
---
## Getting Started
### Prerequisites
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed
- Port `8054` available on the host (or change it in `docker-compose.yml`)
### 1. Clone the repo
```bash
git clone <your-repo-url>
cd homeschool
```
### 2. Create your `.env` file
```bash
cp .env.example .env
```
Open `.env` and fill in the values:
```env
MYSQL_ROOT_PASSWORD=your_secure_root_password
MYSQL_DATABASE=homeschool
MYSQL_USER=homeschool
MYSQL_PASSWORD=your_secure_db_password
# Generate with: openssl rand -hex 32
SECRET_KEY=your_generated_secret_key
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=30
# Comma-separated list of allowed origins (no trailing slash)
# IMPORTANT: Add every domain/IP you access the app from.
# If you use a reverse proxy (e.g. Nginx Proxy Manager) with a custom domain,
# you MUST add that domain here or API requests will be blocked by the browser.
# Example: CORS_ORIGINS=http://localhost:8054,https://homeschool.example.com
CORS_ORIGINS=http://localhost:8054
# Super admin credentials (for /super-admin)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change_me_admin_password
```
### 3. Build and start
```bash
docker compose up --build
```
The first build takes a few minutes (npm install + pip install). On subsequent starts it's fast.
### 4. Register your parent account
Open **http://localhost:8054/login** and register. This creates your admin account.
### 5. Set up your data (in order)
1. **Admin** (`/admin`) → Add each child, pick a color
2. **Admin** → Add subjects (Math, Reading, Science, etc.) with emoji icons and colors. Add activity options to each subject — they appear on the TV dashboard during that block. The **Meeting** subject is created automatically and cannot be deleted or renamed, but you can add activity options (agenda items) to it.
3. **Admin** → Add **Morning Routine** items — these show on the TV during the greeting before the first block starts.
4. **Admin** → Add **Break Activities** items — these show on the TV center panel whenever a break is active.
5. **Admin** → Scroll to **Settings** (below Schedules) and select your local timezone. You can also change your account password here.
6. **Admin** → Scroll to **Schedules** → Create a schedule template, add time blocks assigned to subjects. For any block that should include a break, check **Break** and enter the break duration in minutes.
7. **Dashboard** (`/dashboard`) → Click "Start Day", choose a template
8. **TV** → Open `http://your-lan-ip:8054/tv/1` on the living room TV (replace `1` with the child's ID)
---
## Usage
### Parent Views (require login)
| URL | Description |
|-----|-------------|
| `/dashboard` | Overview, start/stop sessions, select and time blocks, issue behavior strikes |
| `/logs` | Browse timer and strike event history and manual notes; filter by child and date |
| `/admin` | Manage children, subjects (with activity options), morning routine, break activities, schedule templates, and account settings (timezone, password). Includes a Buy Me a Coffee support link at the top of the page. |
### Super Admin Views
| URL | Description |
|-----|-------------|
| `/super-admin/login` | Log in with the `ADMIN_USERNAME` / `ADMIN_PASSWORD` from `.env` |
| `/super-admin` | Manage all registered parent accounts |
Super admin actions per user: **Enter as User** (impersonate), **Reset Password**, **Disable / Enable** login access, **Delete** account and all data. Each user card also shows the account status badge, join date, and last active date (in the user's own timezone).
While impersonating, a yellow banner appears at the top of every page showing who you're viewing as, with an **Exit to Admin Panel** button to return.
### Dashboard Controls
While a session is active, clicking a block in the schedule list **selects** it as the current block without starting the timer. The action buttons provide explicit control and span the full width of the card for easy tapping on a tablet:
**Main block timer:**
| Button | Condition | Action |
|--------|-----------|--------|
| **Start** | Block selected, never timed | Begin counting from zero |
| **Resume** | Block was previously paused | Continue from saved elapsed time |
| **Pause** | Timer is running | Stop counting, save elapsed time |
| **Reset** | Any current block | Clear elapsed to zero; timer stays paused |
| **Done** | Any current block | Snap timer to full duration (00:00 / "Done!"), mark block complete; logs "Marked Done by User" |
| **End Day** | Session active | Mark the session complete |
Pressing **Reset** after **Done** fully un-marks the block — removes the checkmark, clears the completed state in the database, and treats the block as if it were never started.
**Break timer** (shown when the current block has Break Time enabled):
| Button | Condition | Action |
|--------|-----------|--------|
| **Start Break** | Break not yet started | Begin break countdown from zero |
| **Pause** | Break timer running | Stop break countdown |
| **Resume** | Break timer paused | Continue break countdown |
| **Reset** | Break in progress | Clear break elapsed to zero and restart immediately |
### TV Dashboard (no login)
| URL | Description |
|-----|-------------|
| `/tv/:childId` | Full-screen display — greeting + morning routine, current block timer with subject activities, break timer with break activities, day progress bar, schedule sidebar, meeting warning toasts, meeting start overlay |
Point a browser on the living room TV at `http://your-lan-ip:8054/tv/1`. The page connects via WebSocket and updates automatically when a parent starts/stops/advances the timer from the Dashboard.
### API Documentation
FastAPI auto-generates interactive API docs:
- **Swagger UI** — `http://localhost:8054/api/docs`
- **ReDoc** — `http://localhost:8054/api/redoc`
---
## Schema Migrations
The app automatically creates all database tables on startup via SQLAlchemy's `create_all`. Additive column changes are applied with idempotent `ALTER TABLE` statements in the startup lifespan, so upgrading to a new version is safe — just rebuild and restart:
```bash
docker compose build
docker compose up -d
```
No separate migration tool or manual steps are required.
---
## WebSocket Events
The TV dashboard connects to `ws://host/ws/{child_id}` and receives JSON events:
| Event | Triggered by | Key payload fields |
|-------|-------------|---------|
| `session_update` | Session start | Full session snapshot including blocks, morning routine, break activities, and day times |
| `start` | Block timer started | `block_id`, `current_block_id`, `block_elapsed_seconds`, `prev_block_id`, `prev_block_elapsed_seconds` |
| `pause` | Block timer paused | `block_id`, `current_block_id` |
| `resume` | Block timer resumed | `block_id`, `current_block_id` |
| `reset` | Block timer reset to zero | `block_id`, `current_block_id`, `block_elapsed_seconds` (always 0), `uncomplete_block_id` |
| `complete` | Block marked done / session ended | `block_id` (if block done), `is_active: false` (if session ended) |
| `break_start` | Break timer started | `block_id`, `current_block_id`, `break_elapsed_seconds`, `block_elapsed_seconds` |
| `break_pause` | Break timer paused | `block_id`, `current_block_id` |
| `break_resume` | Break timer resumed | `block_id`, `current_block_id` |
| `break_reset` | Break timer reset to zero | `block_id`, `current_block_id`, `break_elapsed_seconds` (always 0) |
| `strikes_update` | Strike issued/cleared/midnight reset | `strikes` |
**Notes:**
- `block_elapsed_seconds` on `start` carries the authoritative accumulated elapsed time so every client can restore the correct timer offset without a local cache.
- `prev_block_id` and `prev_block_elapsed_seconds` on `start` carry the saved elapsed for the block being left, so the TV sidebar immediately shows the correct remaining time for that block.
- `uncomplete_block_id` on `reset` tells clients to remove that block from the completed list (used when Reset is pressed after Done).
- `select` events are broadcast via WebSocket but are **not** persisted to the database or shown in the activity log.
- Implicit `pause` events (written when switching blocks or starting a break) are only recorded if the block's timer was actually running — no duplicate pauses are written if the block was already paused or never started.
- Break timer events (`break_*`) do not affect block selection or elapsed time for the main block timer.
---
## Activity Log Event Types
| Event | Display label | When recorded |
|-------|--------------|---------------|
| `session_start` | Day started | New session begins |
| `start` | Started | Block timer starts |
| `pause` | Paused | Block timer stops (manual or implicit on block switch) |
| `resume` | Resumed | Block timer continues after a pause |
| `reset` | Reset | Block timer cleared to zero |
| `complete` (no block) | Day ended | Session marked complete |
| `complete` (with block) | Marked Done by User | Done button pressed on a block |
| `break_start` | Break started | Break timer starts |
| `break_pause` | Break paused | Break timer stops |
| `break_resume` | Break resumed | Break timer continues |
| `break_reset` | Break reset | Break timer cleared |
`select` events are not recorded in the activity log.
---
## Environment Variables Reference
| Variable | Required | Description |
|----------|----------|-------------|
| `MYSQL_ROOT_PASSWORD` | Yes | MySQL root password |
| `MYSQL_DATABASE` | Yes | Database name (default: `homeschool`) |
| `MYSQL_USER` | Yes | App database user |
| `MYSQL_PASSWORD` | Yes | App database password |
| `SECRET_KEY` | Yes | JWT signing key — generate with `openssl rand -hex 32` |
| `ALGORITHM` | No | JWT algorithm (default: `HS256`) |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | No | Access token lifetime (default: `30`) |
| `REFRESH_TOKEN_EXPIRE_DAYS` | No | Refresh token lifetime (default: `30`) |
| `CORS_ORIGINS` | No | Comma-separated allowed origins (default: `http://localhost:8054`). **Add every domain you access the app from**, including any custom domain behind a reverse proxy (e.g. `http://localhost:8054,https://homeschool.example.com`). Missing an origin causes API requests to be silently blocked by the browser. |
| `ADMIN_USERNAME` | No | Super admin login username (default: `admin`) |
| `ADMIN_PASSWORD` | No | Super admin login password (default: `change_me_admin_password`) |
> **Note:** `ADMIN_USERNAME` and `ADMIN_PASSWORD` must be set in `.env` **and** listed in the `backend` service's `environment` block in `docker-compose.yml`. Changing them in `.env` alone is not sufficient — the backend container reads them as environment variables, not from the file directly.
---
## Stopping and Restarting
```bash
# Stop containers (data preserved in Docker volume)
docker compose down
# Stop and wipe the database volume (full reset)
docker compose down -v
# Restart without rebuilding
docker compose up
```