7 KiB
Imported from .github/copilot-instructions.md in the go-guess repository.
Copilot instructions
Build, test, and lint
Run repository-wide commands from the root:
make dev # run PostgreSQL, RabbitMQ, API, and frontend with development fixtures
make test # complete suite, including disposable infrastructure integration tests
make test-integration # Go tests; starts PostgreSQL and RabbitMQ Testcontainers
make test-env # isolated seeded UI stack at http://localhost:15173
make test-env-down # remove the test stack and its PostgreSQL volume
make lint # go vet and ESLint
make build # compile the API and production frontend
Focused tests:
cd backend && go test ./internal/service -run '^TestCandidatesRequireSixtyPercent$'
cd backend && go test -tags=integration ./internal/integration -run '^TestCompleteInterviewWorkflow$'
cd frontend && npm test -- src/app/App.test.tsx -t "filters jobs by title"
The required toolchain is Go 1.27.1 and React 19.3 with TypeScript/Vite. Use
cd frontend && npm run format:check for frontend formatting and gofmt for Go.
Architecture
backend/cmd/apiwires configuration, PostgreSQL, security, services, handlers, and the Chi router. Requests flow throughinternal/web/{router,handler,model,mapper}tointernal/service/{model,services}and theninternal/database/{repository,mapper,entity}. Never expose GORM entities from handlers.backend/cmd/seedapplies embedded environment-aware fixtures.backend/internal/eventbuspublishes note and shared-document events to the durablego-guess.interview-eventsRabbitMQ fanout exchange. Each API instance consumes through its own exclusive queue and distributes interview-filtered events to authenticated interviewer SSE streams or token-authorized participant streams.frontend/src/appowns application routing and providers,src/featuresgroups feature pages and components,src/componentscontains reusable UI,src/api/client.tsis the typed HTTP boundary,src/typescontains client domain types, andsrc/styles/global.csscontains application-wide styles. Keep feature-specific components inside their feature directory. Vite proxies/apito the Go service in development.- Multi-tenant URLs use
<tenant>.<app domain>, where the app domain isguess-dev.urpi.bein local development and the Kubernetes development profile, andguess.urpi.bein production. The frontend derives the tenant from the hostname, calls same-origin/apithrough the HTTPS proxy, and sendsX-Tenant-Idon every request type. Generated participant links must use the validated matching tenant origin. Keep this contract documented inREADME.mdandfrontend/README.md. - Protected interviewer routes require a bearer JWT only. Go Loose is used for browser login, not for API-key authorization.
Goose files in backend/migrations are schema-only and run in every environment.
internal/database/seed/common.sql contains shared reference data;
development.sql and test.sql contain idempotent environment-specific fixtures.
Every entity change requires a migration plus corresponding fixture and integration
test updates. Never put demo users or test records in schema migrations.
Authentication uses bcrypt passwords and HMAC-signed bearer JWTs. Domain routes are
protected except /api/health, /api/openapi.json, /api/auth/login,
/api/auth/callback, /api/auth/logout, /api/auth/session, and opaque-token
participant interview routes. Any tenant host starts browser login with
github.com/danyel/go-loose/client: /api/auth/login redirects to
https://<tenant>.<GO_LOOSE_AUTH_DOMAIN>/connect/authorize and Go Loose returns to
/api/auth/callback. GO_LOOSE_AUTH_DOMAIN is auth-dev.urpi.be locally and in
the development profile, and auth.urpi.be in production; GO_LOOSE_APP_DOMAIN
is the matching application domain. Both default to the development domains.
GET /api/auth/session answers 404 when no Go Loose login is configured for the
request host, and the frontend uses that to fall back to the password form instead
of redirecting. Go Loose client credentials come from uncommitted environment
variables. The tenant comes from X-Tenant-Id, or from the request host and
X-Forwarded-Host when the header is absent; missing tenant context uses the
public schema. Never expose password hashes, CV/photo bytes, or interviewer
reference answers in public JSON.
Questions are permanent reusable records linked to jobs through
job_posting_questions; detach links rather than deleting questions. Deprecated
questions remain searchable but cannot be newly attached. Question values are
open, multiple_choice, radio, and code_review. Choice types require at least
two options. Open/code-review types require referenceAnswer; code review also
requires codeSnippet.
Jobs move through draft, published, and deprecated. Publishing requires a
question, only published jobs can create invitations, and candidate matching uses
labels plus both skill groups with a 60% threshold. Invitation URLs use
/participant/:invitationId, where the identifier is an opaque token. Participant
questions are acceptance-gated and answers are saved before final submission;
interviewer review uses
GET /api/jobs/{id}/invitations/{invitationId}.
Only completed assessment invitations can receive a passed or failed outcome,
and only passed candidates can be scheduled for an interview. Scheduled
interviews include the creator plus selected co-interviewers. Attendees manage
their invitation through /api/invitations, see accepted interviews in /api/schedule,
and can write notes only after the interview has started. Notes are private to
authenticated attendees. The opaque /participant/meeting/:candidateToken view
exposes only candidate-safe schedule and shared-document data.
Repository conventions
- Prefix every Go interface name with
I. - Keep web, service, and persistence models separate and convert them in adjacent
mapperpackages. - Participant creation uses multipart fields
firstName,lastName,birthday,email,contactInfo,photo, andcv. - Return explicit errors; map expected failures to HTTP status codes and log unexpected failures instead of silently falling back.
- Update the directly related local documentation whenever behavior, configuration, architecture, commands, or public API surfaces change.
- Register backend routes through the typed OpenAPI-aware router helper. The
runtime
/api/openapi.jsondocument is the API source of truth; do not add a manually maintained route inventory or require a generation command. - Commit every repository change with a meaningful conventional prefix such as
feat:,fix:,refactor:,test:,docs:, orchore:.