# CLAUDE.md This file provides guidance to Claude Code when working with this repository. ## Project Overview ActaLog is a mobile-first CrossFit workout tracker built with: - **Backend:** Go (Chi router), SQLite/PostgreSQL/MySQL - **Frontend:** Vue.js 3, Vuetify 3, Pinia - **Architecture:** Clean Architecture with strict layer separation **Version:** 1.3.2 ## Quick Reference ```bash # Backend make build # Build (auto-increments build number) make run # Run on :8080 make dev # Run with hot reload (requires air) make test # Run all tests make lint # Run linter make fmt # Format code # Frontend (from web/) npm run dev # Dev server on :3000 npm run build # Production build npm run test:run # Run tests once npm run test:coverage # Tests with coverage npm run lint:fix # Fix linting issues # Docker ./docker/scripts/build.sh dev # Build image with dev tag docker tag ghcr.io/johnzastrow/actalog:dev ghcr.io/johnzastrow/actalog:latest docker tag ghcr.io/johnzastrow/actalog:dev ghcr.io/johnzastrow/actalog: ./docker/scripts/push.sh dev # Push dev tag ./docker/scripts/push.sh latest # Push latest tag ./docker/scripts/push.sh # Push version tag (e.g., 1.2.0-beta) # Migrations make migrate-create name=add_feature ``` ## Architecture ### Clean Architecture Layers ``` handlers → services → domain ← repositories ``` | Layer | Location | Depends On | Responsibility | |-------|----------|------------|----------------| | Domain | `internal/domain/` | Nothing | Entities, interfaces | | Repository | `internal/repository/` | Domain | Data access | | Service | `internal/service/` | Domain | Business logic | | Handler | `internal/handler/` | Services, Domain | HTTP handling | ### Directory Structure ``` internal/ ├── domain/ # Entities + repository interfaces (ZERO dependencies) ├── repository/ # Data access implementations ├── service/ # Business logic/use cases └── handler/ # HTTP handlers pkg/ ├── auth/ # JWT utilities ├── middleware/ # HTTP middleware ├── prmath/ # 1RM calculation formulas └── version/ # Version management cmd/actalog/ # Application entry point web/ # Vue.js frontend migrations/ # Database migrations ``` ### Key Patterns 1. **Dependency Injection** - All dependencies via constructors 2. **Interface-Driven** - Domain defines interfaces, others implement 3. **Repository Pattern** - Data access abstracted through interfaces 4. **No Global State** - Everything passed explicitly ## Database **Drivers** (set `DB_DRIVER` in `.env`): - `sqlite3` - Development default - `postgres` - Production recommended - `mysql` - MySQL/MariaDB **Query Placeholders:** - SQLite/MySQL: `?` - PostgreSQL: `$1, $2, ...` **Important:** SQLite driver name must be `"sqlite3"` (not `"sqlite"`). ## Development Workflow **Local Development:** 1. Terminal 1: `make run` (backend on :8080) 2. Terminal 2: `cd web && npm run dev` (frontend on :3000) 3. Vite proxy forwards `/api` and `/uploads` to backend **Production (Docker):** - Single port :8080 serves both API and static frontend - No separate Node.js process - `cmd/actalog/main.go:418-436` handles static file serving ## Feature Testing Workflow **Default: Test features using Docker with multiple database backends.** When testing new features, cycle through all supported databases to ensure compatibility: | Database | Connection | |----------|------------| | SQLite | Local file: `./data/actalog.db` | | MariaDB | `192.168.1.234:3306` (user: jcz) | | PostgreSQL | `192.168.1.143:5432` (database: jcz, schema: actalog) | **Email Testing (SMTP2GO):** | Setting | Value | |---------|-------| | SMTP Server | `mail.smtp2go.com` | | SMTP Port | `2525` | | SMTP User | `acta@northredoubt.com` | | SMTP Password | Set `$SMTP_PASSWORD` env var | **Docker Testing Commands:** ```bash # Build Docker image ./docker/scripts/build.sh dev # Tag and push to registry (always push to dev, latest, and version tags) docker tag ghcr.io/johnzastrow/actalog:dev ghcr.io/johnzastrow/actalog:latest docker tag ghcr.io/johnzastrow/actalog:dev ghcr.io/johnzastrow/actalog:1.3.2 # Use current version ./docker/scripts/push.sh dev ./docker/scripts/push.sh latest ./docker/scripts/push.sh 1.3.2 # Use current version # Test with SQLite (mount local data directory) docker run -p 8080:8080 -v $(pwd)/data:/app/data \ -e DB_DRIVER=sqlite3 -e DB_NAME=/app/data/actalog.db \ ghcr.io/johnzastrow/actalog:dev # Test with MariaDB (use --network host for external DB access) docker run --network host \ -e DB_DRIVER=mysql -e DB_HOST=192.168.1.234 -e DB_PORT=3306 \ -e DB_USER=jcz -e DB_PASSWORD=$DB_PASSWORD -e DB_NAME=actalog \ ghcr.io/johnzastrow/actalog:dev # Test with MariaDB + Email enabled docker run --network host \ -e DB_DRIVER=mysql -e DB_HOST=192.168.1.234 -e DB_PORT=3306 \ -e DB_USER=jcz -e DB_PASSWORD=$DB_PASSWORD -e DB_NAME=actalog \ -e EMAIL_ENABLED=true -e EMAIL_FROM=acta@northredoubt.com \ -e SMTP_HOST=mail.smtp2go.com -e SMTP_PORT=2525 \ -e SMTP_USER=acta@northredoubt.com -e SMTP_PASSWORD=$SMTP_PASSWORD \ ghcr.io/johnzastrow/actalog:dev # Test with PostgreSQL (use --network host for external DB access) docker run --network host \ -e DB_DRIVER=postgres -e DB_HOST=192.168.1.143 -e DB_PORT=5432 \ -e DB_USER=jcz -e DB_PASSWORD=$DB_PASSWORD -e DB_NAME=jcz -e DB_SCHEMA=actalog \ ghcr.io/johnzastrow/actalog:dev ``` **When to build outside Docker:** - Debugging with IDE/debugger - Running unit tests (`make test`) - Quick iteration during development - When Docker overhead is unnecessary ## Process & Port Management **Before starting any server, always check and clean up:** ```bash # Check what's using port 8080 lsof -i :8080 | grep LISTEN # Kill all actalog processes pkill -9 -f actalog # Kill Docker containers on port 8080 docker ps --filter "publish=8080" -q | xargs -r docker stop # Full cleanup (processes + containers) pkill -9 -f actalog 2>/dev/null; docker stop $(docker ps -q --filter "publish=8080") 2>/dev/null; sleep 1 ``` **Standard startup sequence:** 1. Kill existing processes: `pkill -9 -f actalog` 2. Verify port is free: `lsof -i :8080 | grep LISTEN` (should be empty) 3. Start server 4. Verify running: `pgrep -f actalog` or `docker ps` ## Code Style ### Go - `make fmt` and `make lint` before committing - Always handle errors explicitly (never `_`) - Wrap errors: `fmt.Errorf("context: %w", err)` - Keep functions focused (single responsibility) ### Vue.js - Composition API with `