# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Important Guidelines **⚠️ DO NOT auto-commit code changes.** Always ask the user before committing. Only create commits when the user explicitly requests it with commands like "commit this work" or "create a commit". ## Temporarily Disabled Features ### Kanban Cancel Column (disabled) The "Cancelled" column has been temporarily hidden from the kanban board API response to reduce UI clutter. The underlying code is preserved and can be re-enabled by searching for `TEMPORARILY DISABLED - Cancel column hidden from kanban` in `internal/repositories/task_repo.go`. **What's disabled:** - Cancel column not returned in kanban API responses (`/api/tasks/` and `/api/tasks/by-residence/:id/`) - `GetCancelledTasks()` query not executed **What's preserved:** - `ColumnCancelled` constant in categorization - `CancelledHandler` and `ArchivedHandler` in categorization chain - `IsCancelled()` predicate - `GetCancelledTasks()` function - All database models and fields (`is_cancelled`, `is_archived`) ## Required Reading Before Task-Related Work **STOP! Before writing ANY task-related code, you MUST read:** 📖 **`docs/TASK_LOGIC_ARCHITECTURE.md`** This document contains: - The consolidated task logic architecture (predicates, scopes, categorization) - Developer checklist for adding task features - Common pitfalls (PostgreSQL DATE vs TIMESTAMP, preload requirements) - Examples of correct vs incorrect patterns **Why this matters:** Task logic (completion detection, overdue calculation, kanban categorization) is centralized in `internal/task/`. Duplicating this logic elsewhere causes bugs where different parts of the app show different results. **Quick reference:** ```go // Use these - DON'T write inline task logic import "github.com/treytartt/honeydue-api/internal/task" task.IsCompleted(t) // Check if task is completed task.IsOverdue(t, now) // Check if task is overdue task.ScopeOverdue(now) // GORM scope for overdue tasks task.CategorizeTask(t, 30) // Get kanban column for task ``` ## Project Overview honeyDue API is a Go REST API for the HoneyDue/honeyDue property management platform. It provides backend services for iOS and Android mobile apps built with Kotlin Multiplatform. **Tech Stack:** - **HTTP Framework**: Echo v4 - **ORM**: GORM with PostgreSQL - **Push Notifications**: APNs (apns2) + FCM HTTP API - **Admin Panel**: GoAdmin - **Background Jobs**: Asynq with Redis - **Caching**: Redis - **Logging**: zerolog - **Configuration**: Viper ## Project Structure ``` honeyDueAPI-go/ ├── cmd/ │ ├── api/main.go # API server entry point │ └── worker/main.go # Background worker entry point ├── internal/ │ ├── config/ # Configuration (Viper) │ ├── database/ # Database connection setup │ ├── models/ # GORM models │ ├── repositories/ # Data access layer │ ├── services/ # Business logic │ ├── handlers/ # HTTP handlers (Echo) │ ├── middleware/ # Echo middleware (auth, logging, etc.) │ ├── router/ # Route setup │ ├── dto/ # Request/Response DTOs │ ├── task/ # Task logic (predicates, scopes, categorization) │ ├── push/ # APNs/FCM push notifications │ ├── worker/ # Asynq background jobs │ ├── admin/ # GoAdmin tables │ ├── apperrors/ # Custom error types │ ├── i18n/ # Internationalization │ ├── monitoring/ # Health checks, metrics │ ├── validator/ # Custom validation rules │ └── integration/ # Integration tests ├── migrations/ # SQL migrations ├── seeds/ # Seed data (lookups, test data) ├── docs/ # Documentation ├── docker/ # Docker files ├── go.mod └── Makefile ``` ## Build & Run Commands ```bash # Install dependencies make deps # Run the API server (development) make run # or go run ./cmd/api # Run the background worker make run-worker # Build binaries make build # API only make build-all # API, Worker, Admin # Run tests make test # All tests make test-coverage # With coverage report go test ./internal/handlers # Specific package # Lint and format make lint make fmt # Docker make docker-up # Start all services make docker-down # Stop services make docker-logs # View logs make docker-restart # Restart services # Database migrations make migrate-up make migrate-down ``` ## Architecture Patterns ### Layer Architecture ``` Request → Router → Middleware → Handler → Service → Repository → Database ↓ Response DTO ``` 1. **Handlers** (`internal/handlers/`): HTTP request/response handling, input validation 2. **Services** (`internal/services/`): Business logic, orchestration 3. **Repositories** (`internal/repositories/`): Database operations (GORM) 4. **Models** (`internal/models/`): GORM entities (map to PostgreSQL tables) ### Task Logic Package The `internal/task/` package centralizes all task-related logic: ``` internal/task/ ├── predicates/predicates.go # Pure functions: IsCompleted, IsOverdue, etc. ├── scopes/scopes.go # GORM scopes: ScopeOverdue, ScopeActive, etc. ├── categorization/chain.go # Kanban column determination └── task.go # Re-exports for single import ``` **Always use this package for task logic:** ```go import "github.com/treytartt/honeydue-api/internal/task" // Predicates (in-memory checks) if task.IsCompleted(t) { ... } if task.IsOverdue(t, time.Now()) { ... } // Scopes (database queries) db.Scopes(task.ScopeActive).Scopes(task.ScopeOverdue(now)).Find(&tasks) // Categorization column := task.CategorizeTask(t, 30) // 30 = "due soon" threshold days ``` ### Authentication Token-based authentication using Django's authtoken format for compatibility: ```go // Middleware extracts user from token user := middleware.GetUserFromContext(c) // Protected routes require auth middleware api := e.Group("/api") api.Use(middleware.AuthRequired(db)) ``` ### Error Handling Use structured errors from `internal/apperrors/`: ```go import "github.com/treytartt/honeydue-api/internal/apperrors" // Return typed errors return apperrors.NewNotFoundError("task", taskID) return apperrors.NewValidationError("title is required") return apperrors.NewForbiddenError("not authorized to access this residence") // Handler converts to HTTP response // 404, 400, 403 respectively ``` ## Database ### GORM Models Models map to Django's existing PostgreSQL tables: ```go // internal/models/task.go type Task struct { ID uint `gorm:"primaryKey"` ResidenceID uint `gorm:"column:residence_id"` Title string `gorm:"column:title"` DueDate *time.Time `gorm:"column:due_date;type:date"` NextDueDate *time.Time `gorm:"column:next_due_date;type:date"` IsCancelled bool `gorm:"column:is_cancelled"` IsArchived bool `gorm:"column:is_archived"` InProgress bool `gorm:"column:in_progress"` // ... } func (Task) TableName() string { return "task_task" // Django table name } ``` ### Common Pitfalls 1. **DATE vs TIMESTAMP**: Use `type:date` for date-only fields, not `type:timestamp` 2. **Preloading**: Always preload associations when needed: ```go db.Preload("Completions").Preload("Priority").Find(&task) ``` 3. **Timezone**: Server uses UTC; client sends timezone header for overdue calculations ### Migrations ```bash # Create new migration make migrate-create name=add_new_column # Run migrations make migrate-up # Rollback make migrate-down ``` ## API Endpoints ### Public Endpoints | Method | Path | Description | |--------|------|-------------| | GET | `/api/health/` | Health check | | POST | `/api/auth/login/` | Login | | POST | `/api/auth/register/` | Register | | GET | `/api/static_data/` | Cached lookups (ETag support) | | GET | `/api/upgrade-triggers/` | Subscription upgrade triggers | ### Protected Endpoints (Token Auth) | Method | Path | Description | |--------|------|-------------| | GET | `/api/residences/my/` | User's residences with summaries | | GET | `/api/residences/:id/` | Residence detail | | GET | `/api/tasks/` | All user's tasks (kanban columns) | | GET | `/api/tasks/by-residence/:id/` | Tasks for residence (kanban) | | POST | `/api/tasks/` | Create task | | POST | `/api/tasks/:id/complete/` | Complete task | | GET | `/api/contractors/` | User's contractors | | GET | `/api/documents/` | User's documents | | GET | `/api/subscription/status/` | Subscription status | ## Adding New Features ### Adding a New API Endpoint 1. **Create/update model** in `internal/models/` 2. **Create repository** in `internal/repositories/`: ```go type MyRepository struct { db *gorm.DB } func (r *MyRepository) FindByID(id uint) (*models.MyModel, error) { var m models.MyModel if err := r.db.First(&m, id).Error; err != nil { return nil, err } return &m, nil } ``` 3. **Create service** in `internal/services/`: ```go type MyService struct { repo *repositories.MyRepository } func (s *MyService) DoSomething(id uint) error { // Business logic here } ``` 4. **Create handler** in `internal/handlers/`: ```go type MyHandler struct { service *services.MyService } func (h *MyHandler) Get(c echo.Context) error { id := c.Param("id") result, err := h.service.DoSomething(id) if err != nil { return err } return c.JSON(http.StatusOK, result) } ``` 5. **Register routes** in `internal/router/router.go` ### Adding Task-Related Logic **ALWAYS use the task package. Never write inline task logic.** 1. Add predicate to `internal/task/predicates/predicates.go` 2. Add corresponding scope to `internal/task/scopes/scopes.go` 3. Re-export in `internal/task/task.go` 4. Add tests for both predicate and scope 5. Update `docs/TASK_LOGIC_ARCHITECTURE.md` ## Testing ```bash # Run all tests go test ./... # Run specific package tests go test ./internal/handlers -v # Run with race detection go test -race ./... # Integration tests (requires database) TEST_DATABASE_URL="..." go test ./internal/integration -v ``` ### Test Patterns ```go func TestTaskHandler_Create(t *testing.T) { // Setup test database db := testutil.SetupTestDB(t) // Create handler with dependencies handler := NewTaskHandler(db) // Create test request req := httptest.NewRequest(http.MethodPost, "/api/tasks/", body) rec := httptest.NewRecorder() c := echo.New().NewContext(req, rec) // Execute err := handler.Create(c) // Assert assert.NoError(t, err) assert.Equal(t, http.StatusCreated, rec.Code) } ``` ## Environment Variables | Variable | Description | Required | |----------|-------------|----------| | `PORT` | Server port (default: 8000) | No | | `DEBUG` | Enable debug mode | No | | `SECRET_KEY` | Token signing secret | Yes | | `POSTGRES_HOST` | Database host | Yes | | `POSTGRES_PORT` | Database port | Yes | | `POSTGRES_USER` | Database user | Yes | | `POSTGRES_PASSWORD` | Database password | Yes | | `POSTGRES_DB` | Database name | Yes | | `REDIS_URL` | Redis connection URL | Yes | | `APNS_KEY_ID` | Apple Push key ID | For push | | `APNS_TEAM_ID` | Apple Team ID | For push | | `APNS_TOPIC` | App bundle ID | For push | | `FCM_SERVER_KEY` | Firebase server key | For push | ## Related Documentation - `docs/TASK_LOGIC_ARCHITECTURE.md` - Task logic patterns (MUST READ) - `docs/PUSH_NOTIFICATIONS.md` - Push notification setup - `docs/SUBSCRIPTION_WEBHOOKS.md` - App Store/Play Store webhooks - `docs/DOKKU_SETUP.md` - Production deployment ## Related Repositories - **Mobile App**: `../HoneyDueKMM` - Kotlin Multiplatform iOS/Android app - **Root Docs**: `../CLAUDE.md` - Full-stack documentation