From ab85502415a3265431dec06c504facf721e0855d Mon Sep 17 00:00:00 2001 From: YOUNG Date: Mon, 9 Mar 2026 15:05:18 -0500 Subject: [PATCH] adding documentation in a README for the project --- README.md | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0f9960 --- /dev/null +++ b/README.md @@ -0,0 +1,226 @@ +# Life Dashboard + +Private personal dashboard at `dash.option.design` for tracking weight/body metrics, workouts, and finances with visual trend charts. Single-user app with password auth. + +## Tech Stack + +- **Backend**: Python 3.12 / FastAPI / SQLAlchemy / Alembic +- **Frontend**: React 18 / TypeScript / Vite / Tailwind CSS / Recharts +- **UI Components**: shadcn/ui style (Radix primitives + Tailwind) +- **Auth**: JWT tokens, single-user credentials via env vars +- **DB**: PostgreSQL 16 +- **Infra**: Docker (multi-stage build), Caddy reverse proxy + +## Project Structure + +``` +life/ +├── backend/ +│ ├── app/ +│ │ ├── main.py # FastAPI entry — API routes + SPA serving +│ │ ├── config.py # Settings from env vars +│ │ ├── auth.py # JWT creation + validation +│ │ ├── database.py # SQLAlchemy engine/session +│ │ ├── models/ +│ │ │ ├── weight.py # WeightEntry model +│ │ │ ├── workout.py # Exercise, Workout, WorkoutSet models +│ │ │ └── finance.py # FinanceTransaction, FinanceSnapshot models +│ │ └── routers/ +│ │ ├── auth.py # Login + token validation +│ │ ├── weight.py # Weight CRUD +│ │ ├── workout.py # Workout + exercise CRUD +│ │ └── finance.py # Transaction + snapshot CRUD + summary +│ ├── alembic/ +│ │ └── versions/ # Database migrations +│ ├── alembic.ini +│ └── requirements.txt +├── frontend/ +│ ├── src/ +│ │ ├── App.tsx # Router + protected routes +│ │ ├── main.tsx # React entry +│ │ ├── api/ +│ │ │ ├── client.ts # Fetch wrapper with JWT auth +│ │ │ └── types.ts # TypeScript interfaces +│ │ ├── components/ +│ │ │ ├── layout/Shell.tsx # Sidebar + main content shell +│ │ │ └── ui/ # Button, Input, Card, Label +│ │ └── pages/ +│ │ ├── Login.tsx +│ │ ├── Dashboard.tsx # Overview with summary cards + sparklines +│ │ ├── Weight.tsx # Weight tracker with line chart +│ │ ├── Workouts.tsx # Workout logger with bar chart +│ │ └── Finances.tsx # Transactions + net worth charts +│ ├── package.json +│ ├── tailwind.config.js +│ └── vite.config.ts +├── Dockerfile # Multi-stage: Node build → Python runtime +├── docker-compose.yml # Standalone compose (references external network) +└── .gitignore +``` + +## Local Development + +**Backend:** +```bash +cd backend +pip install -r requirements.txt +# Set DATABASE_URL, AUTH_USERNAME, AUTH_PASSWORD, JWT_SECRET as env vars +alembic upgrade head +uvicorn app.main:app --reload +``` + +**Frontend:** +```bash +cd frontend +npm install +npm run dev +# Vite proxies /api requests to localhost:8000 +``` + +## Deployment + +The app runs as a Docker container on the VPS alongside Caddy, Postgres, and Gitea — all sharing a `web` Docker network. + +**Push changes:** +```bash +# Local machine +git add . +git commit -m "Description of changes" +git push origin main +``` + +**Deploy on VPS:** +```bash +cd /opt/will-dash/life +git pull + +cd /opt/server +docker compose up --build -d life +``` + +The container automatically runs `alembic upgrade head` on startup, so new migrations are applied before the server starts. + +**Check logs:** +```bash +docker compose logs -f life +``` + +## Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `DATABASE_URL` | Postgres connection string | `postgresql://admin:pass@postgres:5432/life` | +| `AUTH_USERNAME` | Login username | `will` | +| `AUTH_PASSWORD` | Login password | (set in .env) | +| `JWT_SECRET` | Secret for signing tokens | (generate with `openssl rand -hex 32`) | + +## How to Add a New Module + +Adding a new tracking module follows a consistent 9-file pattern. Here's a walkthrough using a hypothetical "Clothing" module that tracks what you wore each day (shirt, shorts, shoes). + +### Backend — 2 new files, 2 edits + +**1. Create the model** — `backend/app/models/clothing.py` + +Define a SQLAlchemy model with your table schema: + +```python +from datetime import date, datetime +from sqlalchemy import Date, Text, func +from sqlalchemy.orm import Mapped, mapped_column +from app.database import Base + +class ClothingEntry(Base): + __tablename__ = "clothing_entries" + + id: Mapped[int] = mapped_column(primary_key=True) + date: Mapped[date] = mapped_column(Date, unique=True) + shirt: Mapped[str] = mapped_column(Text) + shorts: Mapped[str] = mapped_column(Text) + shoes: Mapped[str] = mapped_column(Text) + notes: Mapped[str | None] = mapped_column(Text, nullable=True) + created_at: Mapped[datetime] = mapped_column(server_default=func.now()) +``` + +**2. Create the router** — `backend/app/routers/clothing.py` + +Define Pydantic schemas and CRUD endpoints. Copy `routers/weight.py` as a starting point — it's the simplest existing module (flat table, basic CRUD). + +Key pieces: +- `ClothingCreate` (Pydantic model for input) +- `ClothingRead` (extends Create, adds `id`, sets `from_attributes = True`) +- GET (list with optional date filters), POST, PUT, DELETE endpoints +- Router uses `dependencies=[Depends(get_current_user)]` for auth + +**3. Register the model** — edit `backend/app/models/__init__.py` + +```python +from app.models.clothing import ClothingEntry +# Add to __all__ list +``` + +**4. Register the router** — edit `backend/app/main.py` + +```python +from app.routers import clothing +app.include_router(clothing.router) +``` + +### Database — 1 new migration + +**5. Create migration** — `backend/alembic/versions/002_add_clothing.py` + +```python +revision = "002" +down_revision = "001" + +def upgrade(): + op.create_table("clothing_entries", ...) + +def downgrade(): + op.drop_table("clothing_entries") +``` + +### Frontend — 1 new page, 3 edits + +**6. Add TypeScript types** — edit `frontend/src/api/types.ts` + +```typescript +export interface ClothingEntry { + id: number; + date: string; + shirt: string; + shorts: string; + shoes: string; + notes: string | null; +} +``` + +**7. Create the page** — `frontend/src/pages/Clothing.tsx` + +Build a React component with a form, history table, and optional chart. Copy `pages/Weight.tsx` as a template and modify the fields. + +**8. Add the route** — edit `frontend/src/App.tsx` + +```tsx +import Clothing from "@/pages/Clothing"; +// Add inside : +} /> +``` + +**9. Add sidebar link** — edit `frontend/src/components/layout/Shell.tsx` + +```tsx +import { Shirt } from "lucide-react"; +// Add to navItems array: +{ to: "/clothing", label: "Clothing", icon: Shirt } +``` + +### Deploy + +```bash +git add . && git commit -m "Add clothing module" && git push origin main +# VPS: git pull, then docker compose up --build -d life +``` + +The new migration runs automatically on container startup.