adding documentation in a README for the project
This commit is contained in:
226
README.md
Normal file
226
README.md
Normal file
@@ -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 <Routes>:
|
||||
<Route path="/clothing" element={<ProtectedRoute><Clothing /></ProtectedRoute>} />
|
||||
```
|
||||
|
||||
**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.
|
||||
Reference in New Issue
Block a user