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
│   |   ├── cookie/              # Session cookie helpers
│   │   ├── middleware/
│   │   │   ├── auth.go          # Session auth, platform roles
│   │   ├── response/            # JSON response helpers
│   |   └── web/                     # HTTP path/query param helpers
│   ├── config/
│   │   └── config.go            # Env-driven configuration structs
│   ├── ctxutils/                # Context extraction helpers
│   ├── database/
│   │   ├── 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)
│   ├── emailutils/
│   │   ├── 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
│   ├── domains/                # Business logic (one package per domain)
│   ├── storage/
│   │   ├── r2.go                # Cloudflare R2 client
│   │   └── presignable_storage.go
│   ├── tasks/                   # Asynq task definitions
│   └── 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

Domains

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

Repositories

Repositories live in internal/database/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/database/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.