|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +DIY Go API is a RESTful API template backed by PostgreSQL. It demonstrates a multi-tenant B2B SaaS data model with CRUD operations for movies as the demo domain. The project favors the Go standard library, using third-party packages only when necessary. |
| 8 | + |
| 9 | +## Build & Run Commands |
| 10 | + |
| 11 | +**Build tool**: [Mage](https://magefile.org/) (Go-based make alternative), defined in `magefiles/magefile.go`. |
| 12 | + |
| 13 | +| Command | Description | |
| 14 | +|---|---| |
| 15 | +| `mage -v run local` | Run the server locally | |
| 16 | +| `mage -v testall false local` | Run all tests (non-verbose) | |
| 17 | +| `mage -v testall true local` | Run all tests (verbose) | |
| 18 | +| `go test ./...` | Run tests directly (requires env vars set) | |
| 19 | +| `go test -v -run TestFunctionName ./path/to/package` | Run a single test | |
| 20 | +| `mage -v newkey` | Generate a new encryption key | |
| 21 | +| `mage -v cueGenerateGenesisConfig` | Generate genesis config from CUE schemas | |
| 22 | +| `task db-up` | Run database DDL migrations | |
| 23 | +| `mage -v gcp staging` | Build Docker image and deploy to GCP Cloud Run | |
| 24 | + |
| 25 | +The `env` parameter for mage commands (e.g., `local`, `staging`) determines which config profile to load. Config is loaded from `config_profiles.json` or environment variables. |
| 26 | + |
| 27 | +## Architecture |
| 28 | + |
| 29 | +### Layer Structure |
| 30 | + |
| 31 | +``` |
| 32 | +HTTP Request → server (routes/middleware/handlers) → service (business logic) → sqldb/datastore (data access) → PostgreSQL |
| 33 | +``` |
| 34 | + |
| 35 | +- **Root package (`diygoapi.go`)**: Domain types, interfaces (`Datastorer`, service interfaces like `MovieServicer`, `OrgServicer`, `AppServicer`), and request/response structs. |
| 36 | +- **`server/`**: HTTP routing (`routes.go`), middleware chains (`middleware.go`), and handlers (`handlers.go`). Uses `justinas/alice` for composable middleware. Routes use Go 1.22+ method-pattern syntax (e.g., `"POST /api/v1/movies"`). |
| 37 | +- **`service/`**: Business logic implementations. Services are structs with a `Datastorer` field and optional `EncryptionKey`. Transactions managed here. |
| 38 | +- **`sqldb/datastore/`**: SQL queries generated by [sqlc](https://sqlc.dev/). Type-safe database access via `pgx/v5`. |
| 39 | +- **`cmd/`**: CLI wiring — flag parsing via `peterbourgon/ff/v3` (supports flags, env vars, config files), server initialization, dependency injection. |
| 40 | + |
| 41 | +### Middleware Chain |
| 42 | + |
| 43 | +Every route uses a middleware chain built with `alice`. Typical chain: |
| 44 | +``` |
| 45 | +loggerChain → addRequestHandlerPattern → enforceJSONContentType → appHandler → authHandler → authorizeUserHandler → jsonContentTypeResponse → handler |
| 46 | +``` |
| 47 | + |
| 48 | +### Error Handling (errs package) |
| 49 | + |
| 50 | +Based on the Upspin error pattern. Every function declares `const op errs.Op = "package/Function"` at the top and wraps errors with `errs.E(op, ...)`. |
| 51 | + |
| 52 | +Key types: `Op` (operation trace), `Kind` (error classification: Validation, Unauthenticated, Unauthorized, etc.), `Code` (machine-readable), `Param` (related parameter), `Realm` (WWW-Authenticate). |
| 53 | + |
| 54 | +Errors propagate up the call stack with op context. `errs.HTTPErrorResponse()` maps `Kind` to HTTP status codes. Internal/Database errors are not leaked to clients. |
| 55 | + |
| 56 | +### Authentication & Authorization |
| 57 | + |
| 58 | +- OAuth2 via Google (token validated against Google's OAuth2 v2 API) |
| 59 | +- Bearer token required in `Authorization` header on all requests |
| 60 | +- RBAC: Users have roles, roles have permissions, permissions map to resources |
| 61 | +- Context carries `App`, `User`, and `AuthParams` through the request lifecycle |
| 62 | + |
| 63 | +### Database |
| 64 | + |
| 65 | +- PostgreSQL with `pgx/v5` connection pooling |
| 66 | +- Migrations: SQL files in `scripts/db/migrations/up/` (numbered 000–014) |
| 67 | +- Uses PostgreSQL schemas for tenant isolation (search_path) |
| 68 | +- No ORM — raw SQL + sqlc code generation |
| 69 | + |
| 70 | +### Configuration |
| 71 | + |
| 72 | +Flags → environment variables → config file (precedence order). Key env vars: |
| 73 | +`DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_SEARCH_PATH`, `ENCRYPT_KEY`, `PORT`, `LOG_LEVEL`, `LOG_LEVEL_MIN`, `LOG_ERROR_STACK` |
| 74 | + |
| 75 | +### Logging |
| 76 | + |
| 77 | +Structured JSON logging via `rs/zerolog`. Logger passed through request context. GCP-compatible log hooks in `logger/` package. |
| 78 | + |
| 79 | +## Key Conventions |
| 80 | + |
| 81 | +- **Testing**: Uses `frankban/quicktest` (`c := qt.New(t)`), table-driven subtests |
| 82 | +- **Error ops**: Always `const op errs.Op = "package/Function"` as first line, wrap with `errs.E(op, err)` |
| 83 | +- **External IDs**: API responses use external IDs (UUIDs), never internal database IDs |
| 84 | +- **Service constructors**: Services are struct literals with injected dependencies (no constructor functions) |
| 85 | +- **Domain interfaces**: Defined in root package, implemented in `service/` |
0 commit comments