This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
make setup- Setup development environment and install dependenciesmake apigen- Generate GOA API code from design files (run after modifying design/*.go)make build- Build the application for local developmentmake run- Build and run the service locally on port 8080make test- Run all tests with race detection and coveragemake lint- Run golangci-lint for code quality checksmake all- Complete pipeline: setup, lint, test, build
make docker-build- Build Docker imagemake docker-run- Run service in Docker container locallymake helm-install- Deploy to Kubernetes using Helmmake helm-install-local- Deploy to Kubernetes with mock authentication for local testing
This service uses GOA v3 for API design and code generation:
- API design definitions are in
cmd/mailing-list-api/design/ - Generated code is in
gen/directory (never edit manually) - Run
make apigenafter modifying design files - Service implementations are in
cmd/mailing-list-api/service/
The codebase follows hexagonal/clean architecture principles:
Domain Layer (internal/domain/):
model/- Domain entities (GrpsioMailingList, GrpsioService)port/- Interface definitions for external dependencies (auth, project readers, grpsio service)
Infrastructure Layer (internal/infrastructure/):
auth/- JWT authentication using Heimdall tokensnats/- NATS messaging client, JetStream key-value storage, and storage abstractionsmock/- Mock implementations for testing (auth, grpsio service)
Application Layer:
cmd/mailing-list-api/service/- GOA service implementationsinternal/service/- Domain service implementationsgrpsio_service_reader.go,grpsio_service_writer.go- GroupsIO service orchestratorsgrpsio_mailing_list_reader.go,grpsio_mailing_list_writer.go- Mailing list orchestratorsgrpsio_member_reader.go,grpsio_member_writer.go- Member orchestratorsgrpsio_webhook_processor.go- Webhook event processorcommittee_sync_service.go- Committee member synchronization to mailing lists
Middleware Layer (internal/middleware/):
authorization.go- JWT-based authorization middlewarerequest_id.go- Request ID injection middleware
- Uses JWT tokens from Heimdall service
- Principal extraction from custom claims:
HeimdallClaims{Principal, Email} - JWT validation with PS256 algorithm and JWKS endpoint
- Context-based principal propagation using
constants.PrincipalContextID
- JetStream Storage: Key-value storage for services, mailing lists, and members
- Message Publishing: Publishes indexing and access control events
- Event Subscriptions: Subscribes to committee member events for automatic synchronization
- Connection Management: Reconnection handling and readiness checks
- Queue Groups: Uses
lfx-v2-mailing-list-apiqueue for load balancing
lfx.index.groupsio_service- Service indexinglfx.index.groupsio_mailing_list- Mailing list indexinglfx.index.groupsio_member- Member indexinglfx.update_access.groupsio_service- Service access controllfx.delete_all_access.groupsio_service- Service access deletionlfx.update_access.groupsio_mailing_list- Mailing list access controllfx.delete_all_access.groupsio_mailing_list- Mailing list access deletion
lfx.committee-api.committee_member.created- Committee member creation eventslfx.committee-api.committee_member.updated- Committee member update eventslfx.committee-api.committee_member.deleted- Committee member deletion events
Custom error types in pkg/errors/:
NewServiceUnavailable()- For infrastructure failuresNewUnexpected()- For unexpected conditions- Structured logging with slog package throughout
Request-scoped data flows through context.Context:
- Request IDs via middleware
- Principal from JWT auth
- Context keys defined in
pkg/constants/context.go - Storage constants defined in
pkg/constants/storage.go
The service automatically synchronizes committee members to mailing lists:
- Event-Driven: Listens to committee-api events via NATS subscriptions
- Filter-Based Membership: Mailing lists can specify
committee_filters(voting status values) - Member Types:
committee- Members added via committee sync (automatic)direct- Members added directly via API (manual)
- Removal Behavior:
- Public lists: Committee members converted to
directtype when removed from committee - Private lists: Committee members fully deleted when removed from committee
- Public lists: Committee members converted to
- Idempotency: Duplicate events are safely handled
- Define API contract in
cmd/mailing-list-api/design/mailing_list.go - Run
make apigento generate boilerplate - Implement service methods in
cmd/mailing-list-api/service/ - Add domain models to
internal/domain/model/if needed - Create infrastructure adapters in
internal/infrastructure/for external dependencies - Add middleware in
internal/middleware/for cross-cutting concerns
- Unit tests alongside source files (
*_test.go) - Mock implementations in
internal/infrastructure/mock/ - Integration tests use testify/suite patterns
- Run individual test:
go test -v ./path/to/package -run TestName - Run with coverage:
go test -v -race -coverprofile=coverage.out ./... - Always run
make testbefore committing
Environment-based configuration for:
NATS_URL- NATS server connection (default:nats://lfx-platform-nats.lfx.svc.cluster.local:4222)JWT_AUDIENCEandJWKS_URL- Authentication configurationGROUPSIO_SOURCE- Set tomockto bypass Groups.io API callsAUTH_SOURCE- Set tomockto bypass JWT authenticationREPOSITORY_SOURCE- Set tomockto use in-memory storage- Service runs on port 8080 by default
cmd/mailing-list-api/main.go- Application entry point, service initializationcmd/mailing-list-api/committee.go- Committee event subscription setupinternal/service/committee_sync_service.go- Committee synchronization logicinternal/domain/model/committee_events.go- Committee event structurespkg/constants/subjects.go- NATS subject definitions
For local testing with mocks:
export NATS_URL=nats://localhost:4222export AUTH_SOURCE=mockexport REPOSITORY_SOURCE=mockexport GROUPSIO_SOURCE=mockexport JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL="test-user"
For comprehensive integration testing using local Kubernetes cluster:
-
Deploy with Mock Authentication:
make helm-install-local
This deploys the service with:
AUTH_SOURCE=mock- Bypasses JWT validationJWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL=test-super-admin- Mock principalopenfga.enabled=false- Disables authorizationheimdall.enabled=false- Bypasses middleware
-
Run Integration Tests:
./scripts/integration_test_mailing_list.sh
-
Test Individual Endpoints:
# Any Bearer token works with mock auth curl -H "Authorization: Bearer <your-token>" \ http://lfx-v2-mailing-list-service.lfx.svc.cluster.local:8080/services
values.yaml- Production configuration (JWT authentication)values.local.yaml- Local testing override (mock authentication)- Use
-f values.local.yamlfor local deployment only
The service integrates with Groups.io API through a clean orchestrator pattern:
// Orchestrator with nil-safe design
type grpsIOWriterOrchestrator struct {
groupsClient *groupsio.Client // May be nil for mock/disabled mode
}
// Usage pattern throughout service
if o.groupsClient != nil {
result, err := o.groupsClient.CreateGroup(ctx, domain, options)
// Handle Groups.io operations
} else {
// Mock mode: operations bypassed, domain logic continues
}- Production:
GROUPSIO_SOURCE=groupsio- Uses actual Groups.io API client - Testing:
GROUPSIO_SOURCE=mock- Returns nil client, enables pure domain testing - Domain Logic: All business logic flows through
MockRepositoryininternal/infrastructure/mock/grpsio.go - Error Simulation: Comprehensive error testing available through domain mock
- Clean Separation: Infrastructure (HTTP calls) vs Domain (business logic)
- Nil-Safe: Orchestrator gracefully handles disabled Groups.io integration
- Testable: Domain logic fully tested without external API dependencies
- Configurable: Easy switching between mock and real modes
To test committee member synchronization locally:
-
Start the service with mock mode:
export NATS_URL=nats://localhost:4222 export AUTH_SOURCE=mock export REPOSITORY_SOURCE=mock export GROUPSIO_SOURCE=mock export JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL="test-user" make run
-
Publish test committee events to NATS:
- Use
nats pubcommand or NATS client to publish to:lfx.committee-api.committee_member.createdlfx.committee-api.committee_member.updatedlfx.committee-api.committee_member.deleted
- Event payloads defined in
internal/domain/model/committee_events.go
- Use
-
Verify synchronization:
- Check logs for "processing committee member created/updated/deleted event"
- Query mailing list members to verify additions/removals
- Verify member types (
committeevsdirect)
-
Run unit tests:
go test -v ./internal/service/committee_sync_service_test.go