The LFX v2 Meeting Service is a comprehensive microservice that handles all meeting-related operations for the Linux Foundation's LFX platform. Built with Go and the Goa framework, it provides robust CRUD operations for meetings and registrants with NATS JetStream persistence and JWT authentication.
If you just need to run the service without developing on the service, use the Helm chart:
# Install the meeting service
helm upgrade --install lfx-v2-meeting-service ./charts/lfx-v2-meeting-service \
--namespace lfx \
--create-namespace \
--set image.tag=latest-
Prerequisites
- Go 1.24+ installed
- Make installed
- Docker (optional, for containerized development)
- NATS server running (for local testing)
-
Clone and Setup
git clone https://github.com/linuxfoundation/lfx-v2-meeting-service.git cd lfx-v2-meeting-service # Install dependencies and generate API code make deps make apigen
-
Configure Environment (Optional)
# Copy the example environment file and configure it cp .env.example .env # Edit .env with your local settings
-
Run the Service
# Run with default settings make run # Or run with debug logging make debug
The service is built using a clean architecture pattern with the following layers:
- API Layer: Goa-generated HTTP handlers and OpenAPI specifications
- Service Layer: Business logic and orchestration
- Domain Layer: Core business models and interfaces
- Infrastructure Layer: NATS persistence, JWT authentication, and messaging
- Meeting Management: Complete CRUD operations with platform integration (Zoom support)
- Registrant Management: Registration handling with email uniqueness validation
- Historical Tracking: Past meeting records with session tracking and participant attendance
- Webhook Integration: Platform event processing for real-time meeting state updates
- NATS JetStream Storage: Scalable and resilient data persistence across 5 KV buckets
- NATS Messaging: Event-driven communication with other services
- JWT Authentication: Secure API access via Heimdall integration
- OpenAPI Documentation: Auto-generated API specifications
- Comprehensive Testing: Full unit test coverage with mocks
lfx-v2-meeting-service/
βββ cmd/ # Application entry points
β βββ meeting-api/ # Main API server
βββ charts/ # Helm chart for Kubernetes deployment
β βββ lfx-v2-meeting-service/
βββ design/ # Goa API design files
β βββ meeting-svc.go # Main service definition
β βββ meeting_types.go # Meeting type definitions
β βββ registrant_types.go # Registrant type definitions
β βββ types.go # Common type definitions
βββ gen/ # Generated code (DO NOT EDIT)
β βββ http/ # HTTP transport layer
β βββ meeting_service/ # Service interfaces
βββ internal/ # Private application code
β βββ domain/ # Business domain layer
β β βββ models/ # Domain models and conversions
β β βββ errors.go # Domain-specific errors
β β βββ messaging.go # Messaging abstractions
β β βββ repository.go # Repository interfaces
β βββ infrastructure/ # Infrastructure layer
β β βββ auth/ # JWT authentication
β β βββ messaging/ # NATS messaging implementation
β β βββ store/ # NATS KV store repositories
β βββ middleware/ # HTTP middleware
β βββ handlers/ # Message handlers
β βββ service/ # Service layer implementation
βββ pkg/ # Public packages
β βββ constants/ # Application constants
β βββ utils/ # Utility functions
βββ Dockerfile # Container build configuration
βββ Makefile # Build and development commands
βββ go.mod # Go module definitionThe Meeting API service generates a comprehensive set of tags for meetings, registrants, and participants that are sent to the indexer-service. These tags enable searchability and discoverability of meeting-related content through OpenSearch.
When meetings and meeting settings are created or updated, the following tags are automatically generated:
| Field | Tag Format | Example | Purpose |
|---|---|---|---|
| UID | Plain value | 061a110a-7c38-4cd3-bfcf-fc8511a37f35 |
Direct lookup by ID |
| UID | meeting_uid:<value> |
meeting_uid:061a110a-7c38-4cd3-bfcf-fc8511a37f35 |
Namespaced lookup by ID |
| ProjectUID | project_uid:<value> |
project_uid:9493eae5-cd73-4c4a-b28f-3b8ec5280f6c |
Find meetings for a project |
| Committee UIDs | committee_uid:<value> |
committee_uid:cbef1ed5-17dc-4a50-84e2-6cddd70f6878 |
Find meetings for specific committees |
| Title | Plain value | Weekly Technical Steering Committee |
Text search in meeting titles |
| Description | Plain value | Weekly meeting to discuss technical decisions |
Text search in meeting descriptions |
| MeetingType | meeting_type:<value> |
meeting_type:Board |
Filter meetings by type (e.g., Board, Technical) |
When meeting registrants are created or updated, the following tags are automatically generated:
| Field | Tag Format | Example | Purpose |
|---|---|---|---|
| UID | Plain value | f3c5b4e4-f5a8-4902-a48d-e7ad963bf7d1 |
Direct lookup by registrant ID |
| UID | registrant_uid:<value> |
registrant_uid:f3c5b4e4-f5a8-4902-a48d-e7ad963bf7d1 |
Namespaced lookup by registrant ID |
| MeetingUID | meeting_uid:<value> |
meeting_uid:061a110a-7c38-4cd3-bfcf-fc8511a37f35 |
Find registrants for a specific meeting |
| FirstName | Plain value | John |
Text search by first name |
| LastName | Plain value | Doe |
Text search by last name |
| Plain value | john.doe@example.com |
Text search by email address | |
| Username | Plain value | johndoe |
Text search by username |
When past meetings are created or updated, the following tags are automatically generated:
| Field | Tag Format | Example | Purpose |
|---|---|---|---|
| UID | Plain value | ad83feb8-d34d-438e-b203-87c5df64f1e2 |
Direct lookup by past meeting ID |
| UID | past_meeting_uid:<value> |
past_meeting_uid:ad83feb8-d34d-438e-b203-87c5df64f1e2 |
Namespaced lookup by past meeting ID |
| MeetingUID | meeting_uid:<value> |
meeting_uid:061a110a-7c38-4cd3-bfcf-fc8511a37f35 |
Link to original meeting |
| ProjectUID | project_uid:<value> |
project_uid:9493eae5-cd73-4c4a-b28f-3b8ec5280f6c |
Find past meetings for a project |
| Committee UIDs | committee_uid:<value> |
committee_uid:cbef1ed5-17dc-4a50-84e2-6cddd70f6878 |
Find meetings for specific committees |
| OccurrenceID | occurrence_id:<value> |
occurrence_id:occurrence-789-012-345 |
Find specific meeting occurrence |
| Title | Plain value | Weekly TSC Meeting - March 15, 2024 |
Text search in past meeting titles |
| Description | Plain value | Discussed project roadmap and priorities |
Text search in past meeting descriptions |
When past meeting participants are created or updated, the following tags are automatically generated:
| Field | Tag Format | Example | Purpose |
|---|---|---|---|
| UID | Plain value | 899a891c-0079-4ecf-b32c-c75fbc8c471e |
Direct lookup by participant ID |
| UID | past_meeting_participant_uid:<value> |
past_meeting_participant_uid:899a891c-0079-4ecf-b32c-c75fbc8c471e |
Namespaced lookup by participant ID |
| PastMeetingUID | past_meeting_uid:<value> |
past_meeting_uid:8ba24fb9-0d15-45f2-b6e6-11423fae572e |
Find participants for a past meeting |
| MeetingUID | meeting_uid:<value> |
meeting_uid:061a110a-7c38-4cd3-bfcf-fc8511a37f35 |
Link to original meeting |
| FirstName | Plain value | Jane |
Text search by first name |
| LastName | Plain value | Smith |
Text search by last name |
| Username | Plain value | janesmith |
Text search by username |
| Plain value | jane.smith@example.com |
Text search by email address |
Tags serve multiple important purposes in the LFX system:
-
Indexed Search: Tags are indexed in OpenSearch, enabling fast lookups and text searches across meetings, registrants, and participants
-
Relationship Navigation:
- Meeting-project relationships can be traversed using the
project_uidtags - Meeting-committee relationships can be traversed using the
committee_uidtags - Meeting-registrant relationships can be traversed using the
meeting_uidtags - Past meeting-participant relationships can be traversed using the
past_meeting_uidtags
- Meeting-project relationships can be traversed using the
-
Multiple Access Patterns: Both plain value and prefixed tags support different query patterns:
- Plain values support general text search (e.g., "find meetings containing 'TSC'")
- Prefixed values support field-specific search (e.g., "find registrants with email 'john@example.com'")
-
Historical Tracking: Past meetings and participants maintain references to original meetings via
meeting_uidtags, enabling historical analysis and reporting -
Data Synchronization: When meetings, registrants, or participants are updated, their tags are automatically updated, ensuring search results remain current
- Go 1.24+
- Make
- Git
-
Install Dependencies
make deps
This installs:
- Go module dependencies
- Goa CLI for code generation
- golangci-lint for code linting
-
Generate API Code
make apigen
Generates HTTP transport, client, and OpenAPI documentation from design files.
-
Build the Application
make build
Creates the binary in
bin/meeting-api.
# Run with auto-regeneration
make run
# Run with debug logging
make debug
# Build and run binary
make build
./bin/meeting-apiAlways run these before committing:
# Format code
make fmt
# Run linter
make lint
# Run all tests
make test
# Check everything (format + lint + tests)
make check# Run all tests with race detection and coverage
make test
# Run tests with verbose output
make test-verbose
# Generate HTML coverage report
make test-coverage
# Opens coverage/coverage.htmlWriting Tests:
- Place test files alongside source files with
_test.gosuffix - Use table-driven tests for multiple test cases
- Mock external dependencies using the provided mock interfaces
- Achieve high test coverage (aim for >80%)
- Test both happy path and error cases
When modifying the API:
-
Update Design Files in
design/directory -
Regenerate Code:
make apigen
-
Verify Generation:
make verify
-
Run Tests to ensure nothing breaks:
make test
The Zoom integration follows a layered architecture pattern:
Adding New Zoom API Endpoints:
-
Add API Methods in
internal/infrastructure/zoom/api/:- Add new methods to appropriate API client interfaces
- Implement the methods with proper error handling
- Add corresponding mock implementations for testing
-
Update Provider Layer in
internal/infrastructure/zoom/provider.go:- Add business logic methods that orchestrate API calls
- Handle Zoom-specific data transformations
- Implement domain interfaces
-
Add Tests:
# Test the new API methods go test ./internal/infrastructure/zoom/api/... # Test the provider integration go test ./internal/infrastructure/zoom/...
Zoom Package Structure:
internal/infrastructure/zoom/
βββ api/ # Low-level Zoom API clients
β βββ client.go # HTTP client and auth
β βββ meetings.go # Meetings API endpoints
β βββ users.go # Users API endpoints
β βββ mocks/ # Mock implementations
βββ provider.go # Business logic layer
βββ provider_test.go # Integration tests
Environment Variables:
ZOOM_ACCOUNT_ID: OAuth Server-to-Server Account IDZOOM_CLIENT_ID: OAuth App Client IDZOOM_CLIENT_SECRET: OAuth App Client SecretZOOM_WEBHOOK_SECRET_TOKEN: OAuth App webhook secret token
For local development, copy .env.example to .env and fill in your Zoom credentials from 1Password.
| Target | Description |
|---|---|
make all |
Complete build pipeline (clean, deps, apigen, fmt, lint, test, build) |
make deps |
Install dependencies and tools |
make apigen |
Generate API code from design files |
make build |
Build the binary |
make run |
Run the service locally |
make debug |
Run with debug logging |
make test |
Run unit tests |
make test-verbose |
Run tests with verbose output |
make test-coverage |
Generate HTML coverage report |
make lint |
Run code linter |
make fmt |
Format code |
make check |
Verify formatting and run linter |
make verify |
Ensure generated code is up to date |
make clean |
Remove build artifacts |
make docker-build |
Build Docker image |
make helm-install |
Install Helm chart |
make helm-uninstall |
Uninstall Helm chart |
# Run all tests
make test
# Run with verbose output
make test-verbose
# Generate coverage report
make test-coverageThe project follows Go testing best practices:
- Unit Tests: Test individual components in isolation
- Integration Tests: Test component interactions
- Mock Interfaces: Located in
internal/domain/mock.goand other mock files - Test Coverage: Aim for high coverage with meaningful tests
When adding new functionality:
- Write tests first (TDD approach recommended)
- Use table-driven tests for multiple scenarios
- Mock external dependencies using provided interfaces
- Test error conditions not just happy paths
- Keep tests focused and independent
Example test structure:
func TestServiceMethod(t *testing.T) {
tests := []struct {
name string
input InputType
setupMocks func(*MockRepository)
expected ExpectedType
expectError bool
}{
// Test cases here
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}The service includes a Helm chart for Kubernetes deployment:
# Install with default values
make helm-install
# Install with custom values
helm upgrade --install lfx-v2-meeting-service ./charts/lfx-v2-meeting-service \
--namespace lfx \
--values custom-values.yaml
# Install with Zoom integration
helm upgrade --install lfx-v2-meeting-service ./charts/lfx-v2-meeting-service \
--namespace lfx \
--set zoom.accountId="your-zoom-account-id" \
--set zoom.clientId="your-zoom-client-id" \
--set zoom.clientSecret="your-zoom-client-secret"
# View templates
make helm-templates# Build Docker image
make docker-build
# Run with Docker
docker run -p 8080:8080 linuxfoundation/lfx-v2-meeting-service:latestAll POST, PUT, and DELETE endpoints support the X-Sync header to control whether operations are processed synchronously or asynchronously:
Header Format:
X-Sync: true # Synchronous - waits for downstream services
X-Sync: false # Asynchronous (default) - returns immediately
Behavior:
- Asynchronous (default): The API returns immediately after persisting data. Indexing and access control messages are sent to NATS without waiting for responses.
- Synchronous (
X-Sync: true): The API waits for confirmation from downstream services (indexer and FGA-sync) before responding. Uses NATS request/reply pattern with a 10-second timeout.
When to Use Synchronous Mode:
- Integration tests that need to verify downstream effects
- Critical operations requiring confirmation before proceeding
- Admin operations where consistency is more important than performance
When to Use Asynchronous Mode (default):
- Normal user operations where speed is important
- Bulk operations where eventual consistency is acceptable
- High-throughput scenarios
The service uses NATS for event-driven communication with other LFX platform services.
The service publishes messages to the following NATS subjects:
| Subject | Purpose | Message Schema |
|---|---|---|
lfx.index.meeting |
Meeting indexing events | MeetingIndexerMessage |
lfx.index.meeting_settings |
Meeting settings indexing events | MeetingIndexerMessage |
lfx.index.meeting_registrant |
Registrant indexing events | MeetingIndexerMessage |
lfx.update_access.meeting |
Meeting access control updates | MeetingAccessMessage |
lfx.delete_all_access.meeting |
Meeting access control deletion | MeetingAccessMessage |
lfx.put_registrant.meeting |
Registrant access control updates | MeetingRegistrantAccessMessage |
lfx.remove_registrant.meeting |
Registrant access control deletion | MeetingRegistrantAccessMessage |
lfx.meetings-api.meeting_deleted |
Meeting deletion events (internal) | MeetingDeletedMessage |
The service handles incoming messages on these subjects:
| Subject | Purpose |
|---|---|
lfx.meetings-api.get_title |
Meeting title requests |
lfx.meetings-api.meeting_deleted |
Meeting deletion cleanup |
lfx.webhook.zoom.meeting.started |
Zoom meeting started event |
lfx.webhook.zoom.meeting.ended |
Zoom meeting ended event |
lfx.webhook.zoom.meeting.deleted |
Zoom meeting deleted event |
lfx.webhook.zoom.meeting.summary_completed |
Zoom meeting summary completed event |
lfx.webhook.zoom.meeting.participant_joined |
Zoom meeting participant joined event |
lfx.webhook.zoom.meeting.participant_left |
Zoom meeting participant left event |
lfx.webhook.zoom.meeting.recording.completed |
Zoom meeting recording completed event |
lfx.webhook.zoom.meeting.recording.transcript_completed |
Zoom meeting recording transcript completed event |
All message schemas are defined in internal/domain/models/messaging.go. Key schemas include:
- MeetingIndexerMessage: For search indexing operations
- MeetingAccessMessage: For meeting-level access control
- MeetingRegistrantAccessMessage: For registrant-level access control
- MeetingDeletedMessage: For internal cleanup when meetings are deleted
When meetings or registrants are modified, the service automatically:
- Updates NATS KV storage for persistence
- Publishes indexing messages for search services
- Publishes access control messages for permission services
- Handles cleanup messages for cascading deletions
The service automatically generates OpenAPI documentation:
- OpenAPI 2.0:
gen/http/openapi.yaml - OpenAPI 3.0:
gen/http/openapi3.yaml - JSON formats: Also available in
gen/http/
Access the documentation at: http://localhost:8080/openapi.json
| Endpoint | Method | Description |
|---|---|---|
/livez |
GET | Health check |
/readyz |
GET | Readiness check |
/meetings |
GET, POST | List/create meetings |
/meetings/{uid} |
GET, PUT, DELETE | Get/update/delete meeting |
/meetings/{uid}/registrants |
GET, POST | List/create registrants |
/meetings/{uid}/registrants/{id} |
GET, PUT, DELETE | Get/update/delete registrant |
/past_meetings |
GET, POST | List/create past meetings |
/past_meetings/{uid} |
GET, DELETE | Get/delete past meeting |
/past_meetings/{uid}/participants |
GET, POST | List/create past meeting participants |
/past_meetings/{uid}/participants/{id} |
GET, PUT, DELETE | Get/update/delete past meeting participant |
The service can be configured via environment variables:
| Variable | Description | Default |
|---|---|---|
NATS_URL |
NATS server URL | nats://lfx-platform-nats.lfx.svc.cluster.local:4222 |
LOG_LEVEL |
Log level (debug, info, warn, error) | info |
LOG_ADD_SOURCE |
Add source location to logs | true |
JWKS_URL |
JWKS URL for JWT verification | http://lfx-platform-heimdall.lfx.svc.cluster.local:4457/.well-known/jwks |
JWT_AUDIENCE |
JWT token audience | lfx-v2-meeting-service |
SKIP_ETAG_VALIDATION |
Skip ETag validation (dev only) | false |
JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL |
Mock principal for local dev (dev only) | "" |
LFX_ENVIRONMENT |
LFX app domain environment (dev, staging, prod) | prod |
| Variable | Description | Default |
|---|---|---|
ZOOM_ACCOUNT_ID |
Zoom OAuth Server-to-Server Account ID | "" |
ZOOM_CLIENT_ID |
Zoom OAuth App Client ID | "" |
ZOOM_CLIENT_SECRET |
Zoom OAuth App Client Secret | "" |
When all three Zoom variables are configured, the service will automatically integrate with Zoom API for meetings where platform="Zoom".
For webhook development, add this additional environment variable:
| Variable | Description | Default |
|---|---|---|
ZOOM_WEBHOOK_SECRET_TOKEN |
Webhook secret token for signature validation | "" |
To test Zoom webhooks locally, you'll need to expose your local service to receive webhook events from Zoom:
-
Install ngrok: Download from ngrok.com or use package manager:
brew install ngrok # macOS # or download from https://ngrok.com/download
-
Start your local service:
make run # Starts service on localhost:8080 -
Expose your service with ngrok (in a separate terminal):
ngrok http 8080
This creates a public URL like
https://abc123.ngrok.iothat forwards to your local service. -
Configure Zoom webhook URL: In your Zoom App settings, set webhook endpoint to:
https://abc123.ngrok.io/webhooks/zoom -
Set webhook secret: Copy the webhook secret from Zoom App settings to your environment:
export ZOOM_WEBHOOK_SECRET_TOKEN="your_webhook_secret_here"
Supported Zoom Webhook Events:
meeting.started- Meeting beginsmeeting.ended- Meeting concludesmeeting.deleted- Meeting is deletedmeeting.participant_joined- Participant joinsmeeting.participant_left- Participant leavesrecording.completed- Recording is readyrecording.transcript_completed- Transcript is readymeeting.summary_completed- AI summary is ready
Webhook Processing Flow:
- HTTP webhook endpoint validates Zoom signature
- Event published to NATS for async processing
- Service handlers process business logic (no reply expected)
For local development, you may want to override these settings:
export NATS_URL="nats://localhost:4222"
export LOG_LEVEL="debug"
export SKIP_ETAG_VALIDATION="true"
export JWT_AUTH_DISABLED_MOCK_LOCAL_PRINCIPAL="local-dev-user"The service supports distributed tracing via OpenTelemetry. See docs/tracing.md for configuration options, including setup for Jaeger and Kubernetes deployments.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make changes and ensure tests pass (
make test) - Run quality checks (
make check) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Create a Pull Request
- Follow Go conventions and best practices
- Maintain high test coverage
- Write clear, self-documenting code
- Update documentation for API changes
- Run
make checkbefore committing
This project is licensed under the MIT License - see the LICENSE file for details.