This repository powers the Goa4Web services. Follow these conventions when modifying the code base.
The specs/ directory documents the current implementation and architecture. These files are the source of truth for understanding the system.
- Reflection: The specs reflect the current code state.
- Updates: Changes to specification files should only be made when explicitly requested via a prompt.
Refer to specs/query_naming.md for SQL naming conventions and specs/permissions.md for the permissions model.
Configuration values may be supplied in three ways and must be resolved in this order:
- Command line flags
- Values from a config file
- Environment variables
Defaults should only be used when a value is still empty after applying the above rules. See runtimeconfig.GenerateRuntimeConfig for details.
Environment variable names are centralised in config/env.go. Example configuration files live in examples/ and use the same keys.
All const declarations must include a short comment describing their purpose.
Tests must not interact with the real file system. Use in-memory file systems from io/fs or mocks when file access is required. Run all tests with:
go test ./...
To streamline the human approval process, run a full test suite with go test ./... instead of testing individual files or modules. This allows for a single, pre-approved overall test, which is faster for human verification.
SQL query files are compiled using sqlc. Do not manually edit the generated *.sql.go files; instead edit the .sql files under internal/db/ and run sqlc generate.
Avoid using the overrides section in sqlc.yaml; prefer Go type aliases if a different struct name is required.
All database schema changes must include a new migration script in the migrations/ directory (for example 0002.mysql.sql, 0003.mysql.sql). Never modify existing migration files as that would break deployments running older versions. Every migration must also update the schema_version table so deployments can track the current schema state. Bump the ExpectedSchemaVersion constant in handlers/constants.go whenever a new migration is added so tests stay in sync.
Updates to database/schema.mysql.sql must always be accompanied by a corresponding migration file in migrations/. This ensures that the schema definition remains in sync with the applied migrations.
Errors in critical functions like main() or run() must be logged or wrapped using fmt.Errorf with context. Prefer doing both when errors propagate.
All default HTML or text templates must exist as standalone files and be embedded using //go:embed rather than inline string constants.
Forum page templates that are parsed by filename (e.g., core/templates/site/forum/adminTopicsPage.gohtml) should not wrap the entire file in a redundant {{ define "forum/<filename>" }} block.
When tackling bugs or missing features, check if the behaviour can be verified with tests. If so, write a test that fails before changing the implementation. Iterate on your fix until the new test passes.
Before committing, run go mod tidy followed by go fmt ./..., go vet ./..., and golangci-lint to match the CI checks. If go mod tidy fails, continue but mention the error in the PR summary.
Do not add new global variables unless explicitly instructed or already well established. Avoid global state. Use dependency injection (e.g., passing structs via options/constructors) to share state like caches.
- Roles defined in migrations must also be present in
database/seed.sql. These two sources are strongly linked. If a role is not in the seed data (because it is optional), it should not be included in migrations. - If the database setup is blocking frontend verification, it is acceptable to skip it and note that the user may perform manual testing instead.
- For unit tests that require a database connection, it is recommended to mock the
db.Querierinterface to avoid database dependencies. - Do not expose the underlying database connection (
*sql.DB) to the application layer. All database interactions must go through thedb.Querierinterface generated bysqlc. - Do not use the DB directly for anything that can realistically be done using
sqlc. Always add queries to theinternal/db/*.sqlfiles and runsqlc generateto use them. - Queries should be proxied through
CoreData(via methods oncd) and not directly accessed viacd.Queries(). This is so we can cache retrievals and gracefully invalidate caches.
After every successful build or significant code changeset, you must run the following commands to ensure code quality:
go fmt ./...
go vet ./...
go test ./...A CLI tool is available to verify template rendering with mock data. This is useful for generating HTML snapshots for testing or visual verification without running the full server.
When working on HTML changes, use this tool with the -listen flag to start a local server and verify the visual output. You are encouraged to capture screenshots of the changes and share them in the chat to confirm the results.
Note: Do not commit these verification screenshots to the repository unless they are useful for the README or a documentation gallery. Please also do not commit generated binary files such as server_bin.
Usage:
# Render to stdout
./goa4web test verification template -template <path/to/template.gohtml> -data <data.json>
# Render to file
./goa4web test verification template -template <path/to/template.gohtml> -data <data.json> -output <file.html>
# Serve locally (serves static assets too)
./goa4web test verification template -template <path/to/template.gohtml> -data <data.json> -listen :8080The JSON data file should contain the data structure expected by the template (the Dot field) and optional configuration:
{
"Dot": { ... }, // Data passed as {{ . }}
"User": { ... }, // Mocked current user (optional)
"Config": { ... }, // Runtime configuration (optional)
"URL": "..." // Request URL (optional)
}Field types in Dot are automatically fixed:
- Strings in RFC3339 format are converted to
time.Time. - Whole number
float64values are converted toint32to match typical DB IDs.