Total rebrand across all Go API source files: - Go module path: casera-api -> honeydue-api - All imports updated (130+ files) - Docker: containers, images, networks renamed - Email templates: support email, noreply, icon URL - Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com - Bundle IDs: com.tt.casera -> com.tt.honeyDue - IAP product IDs updated - Landing page, admin panel, config defaults - Seeds, CI workflows, Makefile, docs - Database table names preserved (no migration needed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
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:
ColumnCancelledconstant in categorizationCancelledHandlerandArchivedHandlerin categorization chainIsCancelled()predicateGetCancelledTasks()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:
// 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
# 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
- Handlers (
internal/handlers/): HTTP request/response handling, input validation - Services (
internal/services/): Business logic, orchestration - Repositories (
internal/repositories/): Database operations (GORM) - 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:
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:
// 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/:
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:
// 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
- DATE vs TIMESTAMP: Use
type:datefor date-only fields, nottype:timestamp - Preloading: Always preload associations when needed:
db.Preload("Completions").Preload("Priority").Find(&task) - Timezone: Server uses UTC; client sends timezone header for overdue calculations
Migrations
# 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
- Create/update model in
internal/models/ - Create repository in
internal/repositories/: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 } - Create service in
internal/services/:type MyService struct { repo *repositories.MyRepository } func (s *MyService) DoSomething(id uint) error { // Business logic here } - Create handler in
internal/handlers/: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) } - Register routes in
internal/router/router.go
Adding Task-Related Logic
ALWAYS use the task package. Never write inline task logic.
- Add predicate to
internal/task/predicates/predicates.go - Add corresponding scope to
internal/task/scopes/scopes.go - Re-export in
internal/task/task.go - Add tests for both predicate and scope
- Update
docs/TASK_LOGIC_ARCHITECTURE.md
Testing
# 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
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 setupdocs/SUBSCRIPTION_WEBHOOKS.md- App Store/Play Store webhooksdocs/DOKKU_SETUP.md- Production deployment
Related Repositories
- Mobile App:
../HoneyDueKMM- Kotlin Multiplatform iOS/Android app - Root Docs:
../CLAUDE.md- Full-stack documentation