You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Add .env.example with commented explanations for all environment variables
- Rename design/committee.go to design/committee_svc.go for clarity
- Split design/type.go into entity-specific files (committee_type.go,
committee_member_type.go) keeping shared attributes and errors in type.go
- Update setup skill to cover both local and E2E (OrbStack) dev modes
- Expand develop skill with: Heimdall ruleset guidance (OpenFGA relations),
V1 sync compatibility step, and updated file references
- Update goa-patterns.md with type file convention (<entity>_type.go)
- Expand nats-patterns.md with instructions for adding new KV buckets
(local CLI and Helm chart)
- Add v1-sync-patterns.md reference covering lfx-v1-sync-helper update
locations for cross-system field changes
- Update cmd/committee-api/README.md file structure section
Generated with [Claude Code](https://claude.ai/claude-code)
Signed-off-by: Andres Tobon <andrest2455@gmail.com>
-`cmd/committee-api/design/type.go` — Data types used in those endpoints
127
128
128
129
### After modifying design files
@@ -136,6 +137,77 @@ This regenerates everything in `gen/` — never edit files in `gen/` directly, t
136
137
137
138
Read `references/goa-patterns.md` for examples of adding endpoints and types.
138
139
140
+
## Step 5b: Add a Heimdall Ruleset Entry
141
+
142
+
**Every new endpoint must have a corresponding rule in `charts/lfx-v2-committee-service/templates/ruleset.yaml`.** Without it, the request will be blocked at the gateway when deployed.
143
+
144
+
Each rule specifies the HTTP method + path and what authorization check to perform. The check uses OpenFGA, which enforces access based on the user's relation to an object. The authorization model for committees is defined in [`lfx-v2-helm`](https://github.com/linuxfoundation/lfx-v2-helm/blob/main/charts/lfx-platform/templates/openfga/model.yaml). To see the live model in your local cluster:
For self-action endpoints (where the user has no prior OpenFGA relation to the object), skip the `openfga_check` and use `allow_all` — the service layer enforces business rules:
202
+
203
+
```yaml
204
+
{{- if .Values.openfga.enabled }}
205
+
- authorizer: allow_all # no prior relation to check
206
+
{{- else }}
207
+
- authorizer: allow_all
208
+
{{- end }}
209
+
```
210
+
139
211
## Step 6: Data Models and Storage
140
212
141
213
### Domain models (what gets stored)
@@ -200,6 +272,41 @@ Handlers in this service are thin — they:
200
272
201
273
No business logic should live in handlers. If you find yourself writing complex logic here, it belongs in the internal service layer.
202
274
275
+
## Step 8b: V1 Compatibility — Does This Change Need to Sync?
276
+
277
+
This service coexists with a V1 system ([`project-management`](https://github.com/linuxfoundation/project-management)) that exposes the same committee data via its own API ([V1 committee API docs](https://api-gw.platform.linuxfoundation.org/project-service/v1/api-docs#tag/committeeV2)). The two systems are kept in sync by the `lfx-v1-sync-helper` service.
278
+
279
+
**Ask yourself: does this change touch a field that also exists (or should exist) in V1?**
280
+
281
+
If yes, you need changes in **three repos**, not just this one:
282
+
283
+
| Repo | What to update |
284
+
| ---- | -------------- |
285
+
|`lfx-v2-committee-service` (this repo) | V2 domain model + API design |
286
+
|[`project-management`](https://github.com/linuxfoundation/project-management)| V1 API + data model to add/change the field |
287
+
|[`lfx-v1-sync-helper`](https://github.com/linuxfoundation/lfx-v1-sync-helper)| Sync logic that converts between V1 and V2 models |
288
+
289
+
### How the sync works
290
+
291
+
**V2 → V1 (when data is written in V2):**
292
+
Write in V2 → indexed in OpenSearch → triggers `lfx-v1-sync-helper` → writes to V1 (PostgreSQL). The sync helper contains a function that converts the V2 data model to the V1 data model. **Any new V2 field that should appear in V1 must be mapped there.**
293
+
294
+
**V1 → V2 (when data is written in V1):**
295
+
Write in V1 → triggers `lfx-v1-sync-helper` → indexes in V2 (OpenSearch). The sync helper contains a separate function that maps V1 attributes to V2 attributes. **Any new V1 field that should appear in V2 must be mapped there.**
296
+
297
+
### Important: module versioning in lfx-v1-sync-helper
298
+
299
+
The `lfx-v1-sync-helper` imports this committee service as a Go module to use the V2 data model types. If you're adding new fields to the V2 model and updating the sync helper in parallel (before the V2 changes are released and tagged), you must point the sync helper's `go.mod` to your branch version rather than the tagged release:
300
+
301
+
```bash
302
+
# In lfx-v1-sync-helper, point to your in-progress branch
303
+
go get github.com/linuxfoundation/lfx-v2-committee-service@<your-branch-or-commit>
304
+
```
305
+
306
+
Revert this to a tagged version before merging the sync helper PR.
307
+
308
+
Read `references/v1-sync-patterns.md` for details on where exactly to update in `lfx-v1-sync-helper`.
309
+
203
310
## Step 9: Tests
204
311
205
312
Tests live next to the code they test, in `*_test.go` files.
@@ -258,3 +365,4 @@ Provide a clear summary of what was built:
258
365
259
366
-`references/goa-patterns.md` — How to add endpoints and types to the Goa design
Copy file name to clipboardExpand all lines: .claude/skills/develop/references/goa-patterns.md
+12-6Lines changed: 12 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,14 +4,18 @@ Goa is a code generation framework. You write a **design specification** describ
4
4
5
5
## Files
6
6
7
-
-`cmd/committee-api/design/committee.go` — Service and method (endpoint) definitions
8
-
-`cmd/committee-api/design/type.go` — Reusable data types
7
+
-`cmd/committee-api/design/committee_svc.go` — Service and method (endpoint) definitions
8
+
-`cmd/committee-api/design/type.go` — Shared/cross-entity types: common attributes (Version, ETag, BearerToken, CreatedAt, UpdatedAt, etc.) and error types
9
+
-`cmd/committee-api/design/committee_type.go` — Committee entity types and attribute helpers
10
+
-`cmd/committee-api/design/committee_member_type.go` — Committee member entity types and attribute helpers
11
+
12
+
**Type file convention:** Keep `type.go` for shared utilities and error types only. When an entity has enough types to warrant its own file, create `<entity>_type.go` (e.g. `committee_type.go`, `committee_member_type.go`). Do not put committee-specific types in `type.go`.
9
13
10
14
**Never edit files in `gen/`** — they are fully overwritten by `make apigen`.
11
15
12
16
## Adding a New Endpoint
13
17
14
-
### Step 1: Define the method in committee.go
18
+
### Step 1: Define the method in committee_svc.go
15
19
16
20
Add a new `dsl.Method(...)` block inside the existing `dsl.Service(...)` block:
### Step 2: Define new types in type.go (if needed)
55
+
### Step 2: Define new types (if needed)
52
56
53
-
If the endpoint needs new request/response shapes, add types to `type.go`:
57
+
If the endpoint needs new request/response shapes, add types to the appropriate file — `<entity>_type.go` for entity-specific types, or `type.go` for shared utilities:
@@ -75,9 +79,10 @@ After generation, Goa will expect a method on the service struct in `cmd/committ
75
79
76
80
## Reusable Attribute Helpers
77
81
78
-
Look in `type.go` for helper functions like `CommitteeUIDAttribute()`, `BearerTokenAttribute()`, `VersionAttribute()` — use these instead of defining the same attributes repeatedly.
82
+
Helper functions like `CommitteeUIDAttribute()` live in `committee_type.go`, shared ones like `BearerTokenAttribute()` and `VersionAttribute()` live in `type.go` — use these instead of defining the same attributes repeatedly.
79
83
80
84
To add a new reusable attribute:
85
+
81
86
```go
82
87
funcMemberCountAttribute() {
83
88
dsl.Attribute("member_count", dsl.Int, "Number of members in the committee")
@@ -87,6 +92,7 @@ func MemberCountAttribute() {
87
92
## Standard Error Types
88
93
89
94
These are already defined — always use them for consistency:
|`committee-members`|`constants.KVBucketNameCommitteeMembers`| Committee member data |
12
12
13
+
## Adding a New Bucket
14
+
15
+
Each new data entity that needs its own storage requires a bucket. You need to create it in two places: locally for development, and in the Helm chart for Kubernetes deployments.
16
+
17
+
### 1. Local development (nats CLI)
18
+
19
+
Create the bucket manually using the `nats` CLI once your local NATS server is running:
20
+
21
+
```bash
22
+
nats kv add <bucket-name> \
23
+
--history=20 \
24
+
--storage=file \
25
+
--max-value-size=10485760 \
26
+
--max-bucket-size=1073741824
27
+
```
28
+
29
+
Replace `<bucket-name>` with the actual bucket name (e.g. `committee-invites`). Use the same defaults as the existing buckets: history=20, file storage, 10MB max value size, 1GB max bucket size.
30
+
31
+
### 2. Kubernetes deployment (Helm chart)
32
+
33
+
Two files must be updated:
34
+
35
+
**Step 1 — Add the bucket config to `charts/lfx-v2-committee-service/values.yaml`:**
36
+
37
+
```yaml
38
+
# <your_entity>_kv_bucket is the configuration for the KV bucket for storing <your_entity>
39
+
<your_entity>_kv_bucket:
40
+
creation: true
41
+
keep: true
42
+
name: <bucket-name>
43
+
history: 20
44
+
storage: file
45
+
maxValueSize: 10485760# 10MB
46
+
maxBytes: 1073741824# 1GB
47
+
compression: true
48
+
```
49
+
50
+
Follow the naming convention: `<entity>_kv_bucket` for the key (e.g. `committee_invites_kv_bucket`).
51
+
52
+
**Step 2 — Add a `KeyValue` CRD block to `charts/lfx-v2-committee-service/templates/nats-kv-buckets.yaml`:**
53
+
54
+
```yaml
55
+
---
56
+
{{- if .Values.nats.<your_entity>_kv_bucket.creation }}
Add a `---` separator before each new block. The `keep: true` annotation tells Helm to preserve the bucket (and its data) when the chart is uninstalled — always set this to `true` for production buckets.
77
+
78
+
### 3. Register the bucket constant
79
+
80
+
Add the bucket name as a constant in `pkg/constants/storage.go` alongside the existing ones, then initialize it in the NATS client in `internal/infrastructure/nats/client.go`.
81
+
13
82
## How Data Flows
14
83
15
84
The storage layer lives in `internal/infrastructure/nats/storage.go`. It implements the port interfaces from `internal/domain/port/`.
When a data model change in this service (e.g. adding a new field to `CommitteeBase`) also needs to be reflected in the V1 system, three repos must be updated together. This document describes where exactly to make changes in `lfx-v1-sync-helper`.
4
+
5
+
## Repos involved
6
+
7
+
| Repo | Role |
8
+
| ---- | ---- |
9
+
|[`lfx-v2-committee-service`](https://github.com/linuxfoundation/lfx-v2-committee-service)| V2 domain model and API (this repo) |
10
+
|[`project-management`](https://github.com/linuxfoundation/project-management)| V1 API and data model |
11
+
|[`lfx-v1-sync-helper`](https://github.com/linuxfoundation/lfx-v1-sync-helper)| Sync service that bridges V1 ↔ V2 |
12
+
13
+
## V2 → V1 direction (write happens in V2)
14
+
15
+
When a committee is created or updated in V2, the data is indexed in OpenSearch and triggers the sync helper to write it to V1 (PostgreSQL via the V1 Project Service API).
The relevant functions that build the V1 payload from V2 data are:
20
+
21
+
-`syncCommitteeCreateToV1` — handles committee create events from V2
22
+
-`syncCommitteeUpdateToV1` — handles committee update events from V2
23
+
24
+
Add your new field to the `projectServiceCommitteeCreate` / `projectServiceCommitteeUpdate` payload struct and map it from the V2 `data` map inside these functions. Example:
25
+
26
+
```go
27
+
// In syncCommitteeUpdateToV1, add to the payload:
28
+
ChatChannel: data["chat_channel"],
29
+
```
30
+
31
+
If the field requires a value transformation between V2 and V1 formats (like `category` does), add a dedicated mapper function following the pattern of `mapV2CategoryToV1`.
32
+
33
+
## V1 → V2 direction (write happens in V1)
34
+
35
+
When a committee is created or updated in V1 (sourced from Salesforce), the sync helper calls the V2 committee service API to upsert the record.
The relevant functions that build the V2 payload from V1 Salesforce data are:
40
+
41
+
-`mapV1DataToCommitteeCreatePayload` — used when creating a committee in V2 from a V1 event
42
+
-`mapV1DataToCommitteeUpdateBasePayload` — used when updating a committee in V2 from a V1 event
43
+
44
+
Add your new field to the `CreateCommitteePayload` / `UpdateCommitteeBasePayload` struct mapping inside these functions. The V1 field names come from the raw Salesforce field map (e.g. `v1Data["chat_channel__c"]`). Example:
45
+
46
+
```go
47
+
// In mapV1DataToCommitteeCreatePayload:
48
+
ChatChannel: v1Data["chat_channel__c"],
49
+
```
50
+
51
+
If the field requires a value transformation (like `type__c` → category does via `mapTypeToCategory`), add a dedicated mapper function.
52
+
53
+
## Module versioning when working in parallel
54
+
55
+
The `lfx-v1-sync-helper` imports this repo as a Go module to use the V2 data model types:
If your V2 model changes haven't been released and tagged yet, the new fields won't be visible in the sync helper. To develop both in parallel, point the sync helper's `go.mod` to your branch:
62
+
63
+
```bash
64
+
# In lfx-v1-sync-helper
65
+
go get github.com/linuxfoundation/lfx-v2-committee-service@<your-branch-or-commit-sha>
66
+
```
67
+
68
+
Revert to a tagged version before merging the sync helper PR. Coordinate timing so the V2 tag exists before the sync helper is merged.
69
+
70
+
## Checklist for a cross-system field change
71
+
72
+
-[ ] Add the field to the V2 domain model (`internal/domain/model/`)
73
+
-[ ] Expose it in the V2 API design (`cmd/committee-api/design/`)
74
+
-[ ] Add the field to the V1 data model and API in `project-management`
75
+
-[ ] Update `syncCommitteeCreateToV1` / `syncCommitteeUpdateToV1` in `ingest_indexer.go`
76
+
-[ ] Update `mapV1DataToCommitteeCreatePayload` / `mapV1DataToCommitteeUpdateBasePayload` in `handlers_committees.go`
77
+
-[ ] If V2 changes are not yet tagged, update `go.mod` in the sync helper to point to the branch; revert before merging
0 commit comments