From a59ef0f3f5cd9fcec8afb389727c30e594c36ae7 Mon Sep 17 00:00:00 2001 From: Jan Novak Date: Mon, 19 Jan 2026 19:49:47 +0100 Subject: [PATCH] feat: Add project foundation, documentation, and Docker setup Co-Authored-By: Claude Opus 4.5 --- README.md | 228 +++++++++++++++++++++++ backend/Dockerfile.dev | 18 ++ backend/Dockerfile.prod | 22 +++ backend/claude.md | 116 ++++++++++++ backend/go.mod | 67 +++++++ backend/go.sum | 178 ++++++++++++++++++ docker-compose.yml | 40 ++++ plans/001-initial-implementation-plan.md | 186 ++++++++++++++++++ plans/testing-plan.md | 152 +++++++++++++++ project.md | 13 ++ 10 files changed, 1020 insertions(+) create mode 100644 README.md create mode 100644 backend/Dockerfile.dev create mode 100644 backend/Dockerfile.prod create mode 100644 backend/claude.md create mode 100644 backend/go.mod create mode 100644 backend/go.sum create mode 100644 docker-compose.yml create mode 100644 plans/001-initial-implementation-plan.md create mode 100644 plans/testing-plan.md create mode 100644 project.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..77530ab --- /dev/null +++ b/README.md @@ -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 diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..3e4fa21 --- /dev/null +++ b/backend/Dockerfile.dev @@ -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"] diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod new file mode 100644 index 0000000..4686f9c --- /dev/null +++ b/backend/Dockerfile.prod @@ -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"] diff --git a/backend/claude.md b/backend/claude.md new file mode 100644 index 0000000..0ef1ae9 --- /dev/null +++ b/backend/claude.md @@ -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_` (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/... +``` \ No newline at end of file diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..4772bba --- /dev/null +++ b/backend/go.mod @@ -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 +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..1b2114c --- /dev/null +++ b/backend/go.sum @@ -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= diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..09854f4 --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/plans/001-initial-implementation-plan.md b/plans/001-initial-implementation-plan.md new file mode 100644 index 0000000..8663fa7 --- /dev/null +++ b/plans/001-initial-implementation-plan.md @@ -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 diff --git a/plans/testing-plan.md b/plans/testing-plan.md new file mode 100644 index 0000000..a7d2de9 --- /dev/null +++ b/plans/testing-plan.md @@ -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/... +``` diff --git a/project.md b/project.md new file mode 100644 index 0000000..b668aff --- /dev/null +++ b/project.md @@ -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 + \ No newline at end of file