feat: Add project foundation, documentation, and Docker setup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan Novak
2026-01-19 19:49:47 +01:00
commit a59ef0f3f5
10 changed files with 1020 additions and 0 deletions

228
README.md Normal file
View File

@@ -0,0 +1,228 @@
# Training Tracker
A mobile-friendly web application for tracking training activities with a Go backend and PostgreSQL database.
## Project Structure
```
training-tracker/
├── backend/
│ ├── cmd/server/ # Application entry point
│ ├── internal/
│ │ ├── api/ # HTTP handlers
│ │ ├── models/ # Data structures
│ │ └── storage/ # Database layer
│ ├── Dockerfile.dev # Development container
│ ├── Dockerfile.prod # Production container
│ ├── go.mod
│ └── go.sum
├── frontend/
│ ├── index.html
│ ├── css/styles.css
│ └── js/
│ ├── api.js # API client
│ └── app.js # Application logic
├── .gitea/workflows/ # CI/CD pipelines
├── docker-compose.yml
└── README.md
```
## Prerequisites
- Docker and Docker Compose
- Go 1.25.6+ (for local development without Docker)
- A web server or `python3 -m http.server` (for frontend)
## Running with Docker Compose (Recommended)
Start PostgreSQL and the backend:
```bash
docker-compose up -d
```
This starts:
- PostgreSQL on port 5432
- Backend API on port 8080
To view logs:
```bash
docker-compose logs -f backend
```
To stop:
```bash
docker-compose down
```
## Running Manually
### 1. Start PostgreSQL
```bash
docker-compose up -d postgres
```
Or use an existing PostgreSQL instance and set environment variables.
### 2. Run the Backend
```bash
cd backend
export DB_HOST=localhost
export DB_PORT=5432
export DB_USER=postgres
export DB_PASSWORD=postgres
export DB_NAME=training_tracker
go run ./cmd/server
```
### 3. Serve the Frontend
```bash
cd frontend
python3 -m http.server 3000
```
Open http://localhost:3000 in your browser.
## Testing
The backend includes comprehensive unit and integration tests.
### Run Unit Tests
```bash
cd backend
go test ./...
```
### Run Tests with Coverage
```bash
cd backend
go test -cover ./...
```
### Run Integration Tests
Integration tests use testcontainers to spin up a PostgreSQL instance. Docker must be running.
```bash
cd backend
go test -v -tags=integration ./internal/storage/...
```
### Run All Tests
```bash
cd backend
go test ./... && go test -tags=integration ./internal/storage/...
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DB_HOST` | localhost | PostgreSQL host |
| `DB_PORT` | 5432 | PostgreSQL port |
| `DB_USER` | postgres | Database user |
| `DB_PASSWORD` | postgres | Database password |
| `DB_NAME` | training_tracker | Database name |
| `PORT` | 8080 | Backend server port |
## API Endpoints
### Exercises
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/exercises` | List all exercises |
| POST | `/api/exercises` | Create exercise |
| GET | `/api/exercises/:id` | Get exercise by ID |
| PUT | `/api/exercises/:id` | Update exercise |
| DELETE | `/api/exercises/:id` | Delete exercise |
### Training Plans
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/plans` | List all plans |
| POST | `/api/plans` | Create plan |
| GET | `/api/plans/:id` | Get plan with exercises |
| PUT | `/api/plans/:id` | Update plan |
| DELETE | `/api/plans/:id` | Delete plan |
### Sessions
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/sessions` | List all sessions |
| POST | `/api/sessions` | Create session |
| GET | `/api/sessions/:id` | Get session with entries |
| PUT | `/api/sessions/:id` | Update session |
| DELETE | `/api/sessions/:id` | Delete session |
| POST | `/api/sessions/:id/entries` | Add exercise entry |
### Health Check
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/health` | Returns `{"status": "ok"}` |
## API Examples
### Create an Exercise
```bash
curl -X POST http://localhost:8080/api/exercises \
-H "Content-Type: application/json" \
-d '{"name": "Bench Press", "type": "strength", "muscle_group": "Chest"}'
```
### Create a Session
```bash
curl -X POST http://localhost:8080/api/sessions \
-H "Content-Type: application/json" \
-d '{"date": "2026-01-19T10:00:00Z", "notes": "Morning workout"}'
```
### Add Entry to Session
```bash
curl -X POST http://localhost:8080/api/sessions/1/entries \
-H "Content-Type: application/json" \
-d '{"exercise_id": 1, "weight": 80, "sets_completed": 3, "reps": 10}'
```
## Features
- Mobile-first responsive design
- Exercise library (strength and cardio types)
- Training plan templates
- Session logging with:
- Strength: weight, sets, reps
- Cardio: duration, distance, heart rate
- Session history and dashboard
## Production Deployment
Build the production image:
```bash
docker build -f backend/Dockerfile.prod -t training-tracker-backend ./backend
```
The production image uses:
- Distroless base image
- Non-root user
- Pinned image digests for reproducibility
## CI/CD
The project includes a Gitea Actions workflow (`.gitea/workflows/ci.yaml`) that:
1. Runs tests on pull requests
2. Builds and pushes Docker images on merge to main

18
backend/Dockerfile.dev Normal file
View File

@@ -0,0 +1,18 @@
FROM golang:1.25.6-bookworm
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o server ./cmd/server
EXPOSE 8080
CMD ["./server"]

22
backend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,22 @@
# Production Dockerfile with pinned image digests
# golang:1.25.6-alpine
FROM golang@sha256:d9b2e14101f27ec8d09674cd01186798d227bb0daec90e032aeb1cd22ac0f029 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server ./cmd/server
# gcr.io/distroless/static-debian12:nonroot
FROM gcr.io/distroless/static-debian12@sha256:6dcc833df2a475be1a3d7fc951de90ac91a2cb0be237c7578b88722571f77571
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]

116
backend/claude.md Normal file
View File

@@ -0,0 +1,116 @@
## General
- Before scaffolding any service, verify current stable versions from official sources (go.dev, rust-lang.org, nodejs.org, etc.)
- Prefer stdlib over dependencies where reasonable
- No vendoring unless explicitly requested
## Go
- Use latest stable Go version (check https://go.dev/dl/ before starting)
- Use `slog` for structured logging
- `context.Context` first param, always
- No `init()` unless unavoidable
- Errors: prefer `errors.Join` for multiple errors, `fmt.Errorf` with `%w` for wrapping
## Containers
- Multi-stage builds, distroless/scratch final images
- ubuntu based images for the development builds
- Non-root user, read-only rootfs for production builds
- prepare dockerfiles for both development and production
- Pin base image digests in prod Dockerfiles
## K8s Manifests
- Always include resource requests/limits
- Liveness/readiness probes with sane defaults
- PodDisruptionBudgets for replicated workloads
## Code Style
- Fail fast, return early
- Table-driven tests
- No obvious comments
## CI/CD
- Git remote: Gitea at https://[gitea.yourdomain.com](https://gitea.home.hrajfrisbee.cz/)
- CI: Gitea Actions (workflow syntax is GitHub Actions compatible)
- Workflows go in `.gitea/workflows/`
- Use `runs-on: linux-amd64` (or whatever labels your runners have)
- Registry: gitea.home.hrajfrisbee.cz (use `${{ secrets.REGISTRY_TOKEN }}` for auth)
## Testing (Go)
### Go Test Requirements
- **MANDATORY: All new Go code must have corresponding tests**
- Use stdlib `testing` package, no testify unless already in project
- Table-driven tests as default pattern
- Test file next to source: `foo.go``foo_test.go`
- Name tests `Test<Function>_<scenario>` (e.g., `TestCreateUser_duplicateEmail`)
- Use `t.Parallel()` where safe
- Subtests for table entries: `t.Run(tc.name, func(t *testing.T) { ... })`
- Test behavior, not implementation
- Use `testing.Short()` to skip slow integration tests: `if testing.Short() { t.Skip() }`
### Test Structure
- Each test function should test ONE behavior
- Use descriptive test names: `TestExerciseHandler_Create_ValidInput`
- Table-driven test format:
```go
tests := []struct {
name string
input InputType
want ExpectedType
wantErr bool
}{
{"valid input", validInput, expectedOutput, false},
{"empty name", emptyName, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test logic
})
}
```
### Handler Tests (Unit)
- Mock external dependencies (repositories, services)
- Use `httptest.NewRecorder()` and `httptest.NewRequest()`
- Test: success path, validation errors, not found, internal errors
- Mock interfaces defined in `internal/api/interfaces.go`
- Mock implementations in `internal/api/mocks_test.go`
### Repository Tests (Integration)
- Use testcontainers-go for database tests
- Integration tests in `_test.go` with build tag `//go:build integration`
- Clean database state between tests using `truncateTables()`
- Test: CRUD operations, edge cases, transactions, cascades
- Test helpers in `internal/storage/testhelpers_test.go`
### What to Test
- All public functions/methods
- Happy path AND error paths
- Edge cases: empty inputs, nil values, not found
- Validation logic
- Transaction rollback scenarios
### What NOT to Test
- Simple getters/setters
- Third-party library code
- Private helper functions (test through public API)
### Running Tests
```bash
# Run all unit tests (fast)
go test ./...
# Run with verbose output
go test -v ./...
# Run with coverage
go test -cover ./...
# Run only unit tests, skip integration
go test -short ./...
# Run integration tests (requires Docker)
go test -v -tags=integration ./internal/storage/...
# Run specific package
go test -v ./internal/api/...
```

67
backend/go.mod Normal file
View File

@@ -0,0 +1,67 @@
module training-tracker
go 1.25.6
require (
github.com/lib/pq v1.10.9
github.com/testcontainers/testcontainers-go v0.40.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.5.1+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

178
backend/go.sum Normal file
View File

@@ -0,0 +1,178 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk=
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0/go.mod h1:h+u/2KoREGTnTl9UwrQ/g+XhasAT8E6dClclAADeXoQ=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=

40
docker-compose.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: training-tracker-db
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: training_tracker
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
container_name: training-tracker-backend
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: postgres
DB_NAME: training_tracker
PORT: 8080
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:

View File

@@ -0,0 +1,186 @@
# Training Tracker - Implementation Plan
## Project Overview
A mobile-friendly web application for tracking training activities with a Golang backend and SQL/NoSQL database.
---
## Phase 1: Project Foundation
### 1.1 Project Structure
```
training-tracker/
├── backend/
│ ├── cmd/
│ │ └── server/
│ │ └── main.go
│ ├── internal/
│ │ ├── api/ # HTTP handlers
│ │ ├── models/ # Data structures
│ │ ├── storage/ # Database layer
│ │ └── service/ # Business logic
│ ├── go.mod
│ └── go.sum
├── frontend/
│ ├── index.html
│ ├── css/
│ │ └── styles.css
│ ├── js/
│ │ ├── app.js
│ │ ├── api.js
│ │ └── components/
│ └── assets/
├── docker-compose.yml
└── readme.md
```
### 1.2 Database Schema Design
**Core entities:**
- **users** - User accounts (id, username, email, password_hash, created_at)
- **exercises** - Exercise definitions (id, name, type, muscle_group, description)
- **training_plans** - Training plan templates (id, user_id, name, description)
- **plan_exercises** - Exercises in a plan (id, plan_id, exercise_id, sets, reps, duration, order)
- **sessions** - Completed training sessions (id, user_id, plan_id, date, notes)
- **session_entries** - Individual exercise entries (id, session_id, exercise_id, weight, reps, duration, sets_completed)
**Choice:** PostgreSQL (can use Docker for local development)
---
## Phase 2: Backend Development (Golang)
### 2.1 Initial Setup
- Initialize Go module
- Set up HTTP router (standard library `net/http` or `chi`/`gorilla/mux`)
- Configure environment variables
- Set up database connection
### 2.2 API Endpoints
```
# Auth endpoints (deferred to v2)
# POST /api/auth/register
# POST /api/auth/login
Exercises:
GET /api/exercises # List all exercises
POST /api/exercises # Create exercise
GET /api/exercises/:id # Get exercise
PUT /api/exercises/:id # Update exercise
DELETE /api/exercises/:id # Delete exercise
Training Plans:
GET /api/plans # List user's plans
POST /api/plans # Create plan
GET /api/plans/:id # Get plan with exercises
PUT /api/plans/:id # Update plan
DELETE /api/plans/:id # Delete plan
Sessions:
GET /api/sessions # List sessions (with filters)
POST /api/sessions # Create/start session
GET /api/sessions/:id # Get session details
PUT /api/sessions/:id # Update session
DELETE /api/sessions/:id # Delete session
POST /api/sessions/:id/entries # Add entry to session
```
### 2.3 Core Backend Tasks
1. Set up Go project structure and dependencies
2. Set up PostgreSQL via Docker
3. Implement database migrations
4. Create data models and repository layer
5. Implement REST API handlers
6. Add input validation and error handling
7. Implement CORS for frontend communication
8. (v2) Add authentication
---
## Phase 3: Frontend Development (HTML/CSS/JS)
### 3.1 Mobile-First Design
- Responsive CSS with mobile breakpoints
- Touch-friendly UI elements
- Offline-capable design (future PWA)
### 3.2 Core Views/Pages
1. **Dashboard** - Overview of recent sessions, quick actions
2. **Training Plan View** - Display exercises for current session
3. **Session Logger** - Form to enter weights/reps/duration during workout
- Strength: weight, sets, reps
- Cardio: duration, distance, heart rate (optional)
4. **History** - View past sessions and progress
5. **Exercise Library** - Browse and manage exercises
6. (v2) **Login/Register** - Authentication screens
### 3.3 Frontend Tasks
1. Create base HTML structure and navigation
2. Implement responsive CSS (flexbox/grid)
3. Build API client module (fetch wrapper)
4. Create reusable UI components
5. Implement session logging interface
6. Add form validation
7. Implement local storage for offline data entry
---
## Phase 4: Integration & Testing
### 4.1 Integration
- Connect frontend to backend API
- Test all CRUD operations
- Handle loading states and errors
- Implement authentication flow
### 4.2 Testing
- Backend: Unit tests for handlers and services
- Frontend: Manual testing on mobile devices
- API: Integration tests
---
## Phase 5: Deployment (Optional for v1)
- Docker containerization
- Docker Compose for local development
- Basic deployment documentation
---
## Future Enhancements (v2+)
- Strava integration
- Whoop integration
- Progress charts and analytics
- Workout timer
- PWA with offline support
- Export data (CSV/PDF)
---
## Recommended Implementation Order
1. **Backend foundation** - Go project setup, PostgreSQL via Docker, basic models
2. **Core API** - Exercises and sessions endpoints (strength + cardio support)
3. **Frontend skeleton** - HTML structure, CSS, navigation
4. **Session logging** - The core feature (enter workout data)
5. **Training plans** - Plan viewing and management
6. **Polish** - Error handling, validation, UX improvements
7. (v2) **Authentication** - User accounts
---
## Decisions Made
- **Database**: PostgreSQL (via Docker for local dev)
- **Authentication**: None initially (single-user mode), add in v2
- **Exercise types**: Both strength (weight/sets/reps) and cardio (duration/distance/HR)
## Verification
After implementation, verify by:
1. Start PostgreSQL container and backend server
2. Open frontend in browser (mobile viewport)
3. Create a test exercise (e.g., "Bench Press" - strength type)
4. Create a training plan with exercises
5. Log a session with actual data entry
6. View session history

152
plans/testing-plan.md Normal file
View File

@@ -0,0 +1,152 @@
# Backend Testing Plan
## Overview
Add comprehensive tests for the training-tracker backend following Go best practices and the CLAUDE.md guidelines (table-driven tests).
**Scope:** Both unit tests (handlers with mocks) AND integration tests (repositories with testcontainers)
**Plan storage:** Copy this plan to `training-tracker/plans/testing-plan.md`
## Current State
- **No existing tests** - greenfield testing
- 3 packages to test: `api`, `storage`, `models`
- Dependencies: handlers → repositories → database
## Testing Strategy
### 1. Unit Tests for API Handlers
Test HTTP handlers with mocked repositories.
**Files to create:**
- `internal/api/exercises_test.go`
- `internal/api/plans_test.go`
- `internal/api/sessions_test.go`
**Approach:**
- Define repository interfaces
- Create mock implementations
- Use `httptest.NewRecorder()` and `httptest.NewRequest()`
- Table-driven tests for each endpoint
**Test cases per handler:**
- List: empty result, multiple results
- GetByID: found, not found
- Create: valid input, missing required fields, invalid type
- Update: found, not found, validation errors
- Delete: found, not found
### 2. Integration Tests for Storage Layer
Test repositories against real PostgreSQL using testcontainers.
**Files to create:**
- `internal/storage/exercises_test.go`
- `internal/storage/plans_test.go`
- `internal/storage/sessions_test.go`
- `internal/storage/db_test.go`
**Approach:**
- Use `testcontainers-go` to spin up PostgreSQL
- Run migrations before tests
- Test CRUD operations end-to-end
- Test transaction rollback scenarios
**Test cases:**
- CRUD operations for each entity
- Cascading deletes
- Transaction handling in plans (create/update)
- NULL field handling
- Foreign key constraints
### 3. Test File Structure
```
backend/internal/
├── api/
│ ├── exercises_test.go
│ ├── plans_test.go
│ ├── sessions_test.go
│ └── mocks_test.go # Shared mock implementations
├── storage/
│ ├── testhelpers_test.go # Shared test database setup
│ ├── exercises_test.go
│ ├── plans_test.go
│ └── sessions_test.go
└── models/
└── (no tests needed - pure data structures)
```
## Implementation Steps
1. **Create plans folder** and store this plan:
- Create `training-tracker/plans/`
- Copy this plan to `training-tracker/plans/testing-plan.md`
2. **Add test dependencies** to go.mod:
- `github.com/testcontainers/testcontainers-go`
- Use standard library for assertions (no testify)
3. **Create repository interfaces** in `internal/api/`:
- `ExerciseRepository` interface
- `PlanRepository` interface
- `SessionRepository` interface
4. **Create mock implementations** for unit testing handlers
5. **Create test helpers** for integration tests:
- Shared PostgreSQL container setup
- Database cleanup between tests
6. **Write handler unit tests** (table-driven):
- All CRUD operations
- Validation error paths
- Not found scenarios
7. **Write storage integration tests** (table-driven):
- All repository methods
- Edge cases and error conditions
8. **Update CLAUDE.md** with testing instructions
## Files to Modify
- `backend/go.mod` - add test dependencies
- `backend/internal/api/router.go` - extract interfaces for testability
- `backend/CLAUDE.md` - add testing instructions
## Files to Create
- `plans/testing-plan.md` - this plan document
- `backend/internal/api/interfaces.go` - repository interfaces
- `backend/internal/api/mocks_test.go` - mock implementations
- `backend/internal/api/exercises_test.go`
- `backend/internal/api/plans_test.go`
- `backend/internal/api/sessions_test.go`
- `backend/internal/storage/testhelpers_test.go`
- `backend/internal/storage/exercises_test.go`
- `backend/internal/storage/plans_test.go`
- `backend/internal/storage/sessions_test.go`
## Verification
```bash
cd backend
# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run with coverage
go test -cover ./...
# Run only unit tests (fast)
go test -short ./...
# Run specific package
go test -v ./internal/api/...
```

13
project.md Normal file
View File

@@ -0,0 +1,13 @@
# Training tracker
Will be application for tracking my activities relevant to training.
It will have:
- frontend:
- mobile friendly web application which is gonna let me view training plan for the session, will allow to enter session type, weights/resistance, repetitions, duration or other relevant attributes of the session.
- technologies to be used: html, css, javascript
- backend:
- service written in golang available over http/https allowing frontend to access/persist all the necessary information
- persistent layer will ba suitable database, either sql or nosql
- auxiliary services:
- this is something extra which does not have to be part of 1st version of the app and will be able to read data from strava or whoop