Add CLAUDE.md with comprehensive developer documentation

Documents the Go API architecture and patterns:
- Project structure and tech stack (Echo, GORM, Asynq, Redis)
- Task logic package usage (predicates, scopes, categorization)
- Layer architecture (Handler → Service → Repository)
- Build commands, testing, and environment configuration
- API endpoints reference
- Links to related docs (TASK_LOGIC_ARCHITECTURE.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-16 21:04:49 -06:00
parent 156741f1ad
commit b62da4e303

388
CLAUDE.md Normal file
View File

@@ -0,0 +1,388 @@
# 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".
## 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/casera-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
Casera API is a Go REST API for the MyCrib/Casera 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
```
myCribAPI-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/casera-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/casera-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**: `../MyCribKMM` - Kotlin Multiplatform iOS/Android app
- **Root Docs**: `../CLAUDE.md` - Full-stack documentation