Skip to content

Project Structure

apps/api/
├── cmd/
│   ├── api/
│   │   └── main.go              # API server entry point
│   ├── BAT_worker/
│   │   ├── main.go              # Admission calculator worker
│   │   └── Dockerfile
│   └── email_worker/
│       ├── main.go              # Email delivery worker
│       └── Dockerfile
├── internal/
│   ├── api/
│   │   ├── api.go               # Router setup, route registration
│   │   ├── handlers/            # HTTP handlers (one file per domain)
│   │   ├── middleware/
│   │   │   ├── auth.go          # Session auth, platform roles
│   │   │   └── events.go        # Event-scoped role middleware
│   │   └── response/            # JSON response helpers
│   ├── bat/                     # BAT engine logic
│   ├── config/
│   │   └── config.go            # Env-driven configuration structs
│   ├── cookie/                  # Session cookie helpers
│   ├── ctxutils/                # Context extraction helpers
│   ├── db/
│   │   ├── connection.go        # pgxpool setup
│   │   ├── errors.go            # DB error helpers (unique, not found)
│   │   ├── transaction.go       # Transaction manager
│   │   ├── migrations/          # SQL migration files (goose)
│   │   ├── queries/             # Raw SQL query files (sqlc input)
│   │   ├── repository/          # Data access objects
│   │   └── sqlc/                # Generated Go code (do not edit)
│   ├── email/
│   │   ├── ses.go               # AWS SES client
│   │   ├── templates/           # HTML email templates
│   │   └── validation.go        # Email address validation
│   ├── logger/                  # Zerolog initialization
│   ├── oauth/
│   │   └── discord.go           # Discord OAuth2 exchange + user info
│   ├── parse/                   # Generic optional type, safe parsers
│   ├── ptr/                     # Pointer helpers
│   ├── services/                # Business logic (one file per domain)
│   ├── storage/
│   │   ├── r2.go                # Cloudflare R2 client
│   │   └── presignable_storage.go
│   ├── tasks/                   # Asynq task definitions
│   ├── web/                     # HTTP path/query param helpers
│   └── workers/                 # Worker process implementations
├── docs/                        # Generated Swagger/OpenAPI spec
├── Dockerfile                   # Production multi-stage build
├── Dockerfile.dev               # Development build with Air hot reload
├── go.mod
├── go.sum
└── sqlc.yml                     # SQLc codegen configuration

Key Conventions

Handlers

Handlers live in internal/api/handlers/ with one file per domain (e.g., events.go, application.go). Each handler struct receives its service via constructor injection. Handlers are responsible for:

  • Parsing path/query parameters
  • Decoding and validating request bodies
  • Calling the appropriate service method
  • Writing JSON responses

Services

Services live in internal/services/ with one file per domain. They contain all business logic and coordinate between repositories. Services receive repositories via constructor injection and use the transaction manager for operations that require atomicity.

Repositories

Repositories live in internal/db/repository/ and provide a type-safe data access layer over the sqlc-generated queries. Each repository wraps a *sqlc.Queries and exposes domain-specific methods. Repositories support transactions via NewTx(tx).

Generated Code

internal/db/sqlc/ is fully generated by sqlc — never edit it directly. To regenerate after changing a query or migration:

cd apps/api
make generate

Configuration

All configuration is loaded from environment variables via internal/config/config.go. The loader tries .env.local, .env.dev, and .env in order. See Installation for the full variable reference.