Skip to content

[LFXV2-1730] feat: send notification emails when project members are added#65

Open
andrest50 wants to merge 15 commits into
mainfrom
feat/LFXV2-1730-project-role-notifications
Open

[LFXV2-1730] feat: send notification emails when project members are added#65
andrest50 wants to merge 15 commits into
mainfrom
feat/LFXV2-1730-project-role-notifications

Conversation

@andrest50
Copy link
Copy Markdown
Contributor

@andrest50 andrest50 commented May 12, 2026

Summary

  • Subscribes to the existing `lfx.projects-api.project_settings.updated` NATS event inside project-service and diffs old vs new settings to detect newly added writers, auditors, and meeting coordinators
  • For each new member, renders an HTML + plain-text email and fans it out in parallel (errgroup, limit 5) via NATS request/reply to `lfx-v2-email-service` (LFXV2-1768)
  • `PUT /settings` returns immediately — email sending is fully async and best-effort (failures logged, never propagated)
  • Augments `ProjectSettingsUpdatedMessage` with an `Actor` field so the email can name who made the change

Implementation details

New files:

  • `internal/service/project_subscriber.go` — `HandleProjectSettingsUpdated` NATS event handler + member diff helpers (`diffNewMembers`, `diffRole`, `memberKey`)
  • `internal/service/email/project_role_notification.go` — renders HTML + plain-text from embedded Go templates
  • `internal/service/email/templates/project_role_notification.{html,txt}` — email templates

Changed files:

  • `pkg/events/project.go` — added `Actor` struct and field to `ProjectSettingsUpdatedMessage`
  • `internal/domain/message.go` — added `SendEmailRequest` to `MessageBuilder` interface
  • `internal/infrastructure/nats/message.go` — implements `SendEmailRequest` via `RequestMsgWithContext`
  • `internal/infrastructure/nats/mock.go` — adds `RequestMsgWithContext` to `INatsConn` interface + mock
  • `internal/domain/mock.go` — `SendEmailRequest` mock method on `MockMessageBuilder`
  • `internal/service/project_operations.go` — stamps `Actor` from request context on the event
  • `internal/service/project_service.go` — adds `LFXSelfServeBaseURL` to `ServiceConfig`
  • `cmd/project-api/main.go` — wires event subscription loop; adds `LFX_SELF_SERVE_BASE_URL` env var (defaults: `app.lfx.dev` / `staging.app.lfx.dev` / `dev.app.lfx.dev`)
  • `internal/infrastructure/log/`, `internal/infrastructure/middleware/` — moved from `internal/log/` and `internal/middleware/` to enforce strict domain/infrastructure/service layering
  • `go.mod` — adds `lfx-v2-email-service` dependency via published pseudo-version

Dependencies

This PR depends on LFXV2-1768 (`lfx-v2-email-service`) being deployed. The email service code must be merged and the pseudo-version in `go.mod` updated to the final tagged version once that PR merges.

How to test locally

  1. Start NATS: `docker run --rm -p 4222:4222 nats:latest`
  2. Start Mailpit: `docker run --rm -p 1025:1025 -p 8025:8025 axllent/mailpit`
  3. Start email service (from its repo): `SMTP_HOST=localhost SMTP_PORT=1025 EMAIL_ENABLED=true NATS_URL=nats://localhost:4222 make run`
  4. Start project service with mock auth
  5. `PUT /projects/:uid/settings` adding a new writer with email → Mailpit UI at `http://localhost:8025\` shows the notification
  6. Same payload again (no diff) → no email fired
  7. Stop email service, repeat step 5 → project service still returns 200; log shows handler warning

Ticket

LFXV2-1730

🤖 Generated with Claude Code

andrest50 added 4 commits May 11, 2026 16:44
Add pkg/events/project.go with public UserInfo, ProjectSettings, and
ProjectSettingsUpdatedMessage types so downstream services can import
canonical struct definitions without redefining them.

Domain types in internal/domain/models/ remain internal. Explicit
converters in internal/service/converters.go (DomainSettingsToEvent,
domainUsersToEvent, domainUserPtrToEvent) map domain → wire type
before publishing. Removes internal message.go now superseded by
the pkg/events types.

Updates message_test.go to reference events.ProjectSettingsUpdatedMessage
and documents the pkg/events convention in CLAUDE.md.

LFXV2-1730

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Add TestDomainSettingsToEvent to converters_test.go covering nil input,
full field mapping, nil optional pointers, and nil slice → empty slice
behavior. Also fix CLAUDE.md Key Implementation Details section numbering
(2a → 3, renumber 3/4 → 4/5).

LFXV2-1730

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Using make() unconditionally turned nil domain slices into empty slices,
changing JSON serialization from null to [] and breaking wire format
compatibility with existing consumers. Add a nil guard so nil input
returns nil (preserving the original null serialization). Update tests
to document this behavior.

LFXV2-1730

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
…added

Subscribe to the existing lfx.projects-api.project_settings.updated NATS
event and diff old vs new settings to detect newly added writers, auditors,
and meeting coordinators. For each addition, render an HTML + plain-text
email and fan it out in parallel (errgroup, limit 5) via NATS request/reply
to the email service. The HTTP PUT /settings call is unaffected — emails are
fully async and best-effort.

- pkg/events/project.go: add Actor struct and field to ProjectSettingsUpdatedMessage
- internal/service/notifications_diff.go: member diff by username or email
- internal/service/notifications_render.go: embedded HTML + plain-text templates
- internal/service/notifications_handler.go: HandleProjectSettingsUpdated handler
- internal/service/project_operations.go: stamp Actor from request context
- internal/service/project_service.go: add LFXSelfServeBaseURL to ServiceConfig
- internal/domain/message.go: add SendEmailRequest to MessageBuilder interface
- internal/infrastructure/nats/message.go: implement SendEmailRequest via NATS
- internal/infrastructure/nats/mock.go: add RequestMsgWithContext to INatsConn
- internal/domain/mock.go: add SendEmailRequest mock method
- cmd/project-api/main.go: wire event subscription loop, add LFX_SELF_SERVE_BASE_URL env
- go.mod: add lfx-v2-email-service dependency with local replace directive

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
@andrest50 andrest50 requested a review from a team as a code owner May 12, 2026 21:56
Copilot AI review requested due to automatic review settings May 12, 2026 21:56
Comment thread go.mod Fixed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces email notification functionality triggered by project settings changes. It adds NATS request/reply patterns for email sending, defines canonical event types, implements a project settings change handler that detects new role assignments and sends templated notifications, relocates the logging package, and updates configuration and documentation accordingly.

Changes

Email Service Integration and NATS Request/Reply Pattern

Layer / File(s) Summary
Email request domain contract and NATS handler
internal/domain/message.go, internal/domain/mock.go, internal/infrastructure/nats/message.go, internal/infrastructure/nats/mock.go, internal/infrastructure/nats/message_test.go
MessageBuilder interface gains SendEmailRequest that marshals email payloads, sends them via NATS RequestMsgWithContext, and unmarshals error responses. Tests validate success and error paths.

Project Settings Event Processing and Role Notification Workflow

Layer / File(s) Summary
Event wire types and domain conversion
pkg/events/project.go, internal/service/converters.go, internal/service/converters_test.go
Canonical ProjectSettingsUpdatedMessage, ProjectSettings, UserInfo, and Actor types reside in pkg/events/. Domain ProjectSettings objects are converted to event types via DomainSettingsToEvent with helper converters for user records.
Event publishing and handler wiring
internal/service/project_operations.go, cmd/project-api/main.go, internal/infrastructure/nats/message_test.go
UpdateProjectSettings publishes events with old/new settings and actor context via the new event types. Main wires a queue subscription to ProjectSettingsUpdatedSubject that invokes HandleProjectSettingsUpdated and logs handler errors.
Project settings change handler and notification dispatch
internal/service/project_subscriber.go, internal/service/project_subscriber_test.go
HandleProjectSettingsUpdated unmarshals events, diffs old and new role members, constructs inviter display names and project URLs, and concurrently sends templated notifications (with per-recipient timeout and error suppression) to newly assigned users.
Email template rendering and templates
internal/service/email/project_role_notification.go, internal/service/email/project_role_notification_test.go, internal/service/email/templates/project_role_notification.html, internal/service/email/templates/project_role_notification.txt
RenderProjectRoleNotification embeds and parses HTML and text templates at init, then renders them with recipient/project/role/inviter data. Templates include placeholder substitution and static footers.

Infrastructure Reorganization

Layer / File(s) Summary
Log package relocation
internal/infrastructure/middleware/request_id.go, internal/infrastructure/middleware/request_logger.go, internal/service/document_operations.go, internal/service/folder_operations.go, internal/service/link_operations.go, internal/service/project_handlers.go, scripts/root-project-setup/main.go
Multiple files update their logger import path from internal/log to internal/infrastructure/log, consolidating logging utilities into the infrastructure layer.
Configuration and environment setup
internal/service/project_service.go, cmd/project-api/main.go, go.mod
ServiceConfig adds LFXSelfServeBaseURL. Main reads LFX_SELF_SERVE_BASE_URL env var with environment-based defaults and passes it to service config. Email service dependency is added to go.mod; OpenFGA SDK is removed as indirect.
Build rules and documentation
Makefile, .github/workflows/license-header-check.yml, CLAUDE.md
License-check target is rewritten to scan `*.go

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: sending notification emails when project members are added, matching the PR's core objective.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the implementation approach, new files, modified files, dependencies, and testing instructions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-1730-project-role-notifications

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds asynchronous, best-effort notification emails when project settings updates add new members to key roles (writers, auditors, meeting coordinators). It does so by subscribing to the existing lfx.projects-api.project_settings.updated NATS event, diffing old vs new settings, rendering email templates, and fanning out send requests to lfx-v2-email-service.

Changes:

  • Introduces a NATS event handler that diffs settings updates and sends role notification emails (HTML + text) via NATS request/reply to the email service.
  • Moves project-settings-updated event wire types into pkg/events/ and adds domain→event conversion helpers.
  • Wires runtime configuration for LFX Self-Serve base URL and adds the NATS subscription for the new handler.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/events/project.go Adds canonical wire types for project events, including Actor on settings-updated messages.
internal/service/templates/project_role_notification.txt Plain-text email template for role notifications.
internal/service/templates/project_role_notification.html HTML email template for role notifications.
internal/service/project_service.go Extends service config with LFXSelfServeBaseURL.
internal/service/project_operations.go Publishes settings-updated event using pkg/events types and stamps actor username.
internal/service/notifications_render.go Embeds and renders email templates (subject/HTML/text).
internal/service/notifications_render_test.go Adds tests for template rendering output.
internal/service/notifications_handler.go Implements NATS event handler to diff members and send emails via MessageBuilder.
internal/service/notifications_handler_test.go Adds tests covering additions/no-op/send errors/unmarshal behavior.
internal/service/notifications_diff.go Implements diffing logic for newly added role members.
internal/service/notifications_diff_test.go Adds tests for diffing behavior and identity matching rules.
internal/service/converters.go Adds DomainSettingsToEvent and user conversion helpers for event payloads.
internal/service/converters_test.go Adds tests verifying domain→event conversion semantics.
internal/infrastructure/nats/mock.go Extends NATS conn interface + mock with RequestMsgWithContext.
internal/infrastructure/nats/message.go Adds SendEmailRequest implementation using NATS request/reply.
internal/infrastructure/nats/message_test.go Updates event message tests to use pkg/events types.
internal/domain/models/message.go Removes old internal event payload struct (moved to pkg/events).
internal/domain/mock.go Adds mock method for SendEmailRequest.
internal/domain/message.go Extends MessageBuilder interface with SendEmailRequest.
cmd/project-api/main.go Adds env var parsing for base URL and subscribes to settings-updated events.
go.mod Adds email-service dependency and updates Go version; includes a local replace directive.
go.sum Updates dependency checksums accordingly.
CLAUDE.md Documents the pkg/events/ wire-type convention and domain→event converter rule.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread go.mod Outdated
Comment thread internal/infrastructure/nats/message.go
Comment thread internal/service/notifications_handler.go Outdated
- Add 5s timeout (emailSendTimeout) on each SendEmailRequest call so a
  slow or unresponsive email service cannot hold errgroup slots indefinitely
- Remove redundant eh := eh loop-variable shadow (fixed in Go 1.22+)
- Remove dead GetProjectBase mock setup in the "no additions" test case;
  handler exits before calling GetProjectBase when no new members are found

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/service/templates/project_role_notification.txt (1)

1-11: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the required license header in a non-rendered template comment.

CI is currently failing license-header checks for this file. Add the header using Go template comments so it won’t appear in outgoing emails.

Proposed fix
+{{/* Copyright The Linux Foundation and each contributor to LFX. */}}
+{{/* SPDX-License-Identifier: MIT */}}
+
 Hi {{.RecipientName}},
 
 {{.InviterName}} has added you as a {{.Role}} on the {{.ProjectName}} project in LFX Self-Serve.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/templates/project_role_notification.txt` around lines 1 -
11, Add the required license header to the top of the template using a Go
template comment so it is not rendered in outgoing emails; wrap the full license
text inside {{/* ... */}} and place it before any template content (above the
existing usages like {{.RecipientName}}, {{.InviterName}}, {{.Role}},
{{.ProjectName}}, {{.ProjectURL}}) to satisfy the CI license-header checks
without altering the visible email.
🧹 Nitpick comments (5)
internal/service/notifications_diff_test.go (1)

20-84: ⚡ Quick win

Add a duplicate-entry test case for new members.

Please add a table case where the same user appears twice in new.Writers and assert only one roleAssignment is returned. This locks in non-dup notification behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/notifications_diff_test.go` around lines 20 - 84, Add a new
table-test entry in the tests slice that checks deduplication of new members:
create a case (e.g. name "duplicate new writers deduped") where new is
events.ProjectSettings{Writers: []events.UserInfo{alice, alice}} and set
wantLen: 1 and wantContains: []roleAssignment{{User: alice, Role: "Writer"}};
this ensures the code that produces roleAssignment (referenced by roleAssignment
type and the logic comparing old vs new ProjectSettings/Writers) returns only
one assignment for duplicate entries.
internal/service/notifications_render_test.go (1)

14-43: ⚡ Quick win

Convert this into a table-driven test with multiple scenarios.

Current coverage is only one happy path. A table-driven structure (e.g., normal values, empty inviter/recipient, special chars in names/role) would satisfy the test guideline and improve robustness.

Suggested shape
-func TestRenderProjectRoleNotification(t *testing.T) {
-	data := projectRoleNotificationData{ ... }
-	subject, html, text, err := renderProjectRoleNotification(data)
-	...
-}
+func TestRenderProjectRoleNotification(t *testing.T) {
+	tests := []struct {
+		name string
+		data projectRoleNotificationData
+	}{
+		{ name: "happy path", data: projectRoleNotificationData{ ... } },
+		{ name: "empty inviter", data: projectRoleNotificationData{ ... } },
+		{ name: "special chars", data: projectRoleNotificationData{ ... } },
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			subject, html, text, err := renderProjectRoleNotification(tt.data)
+			require.NoError(t, err)
+			// assertions...
+		})
+	}
+}

As per coding guidelines **/*_test.go: Use table-driven tests for comprehensive test coverage with multiple test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/notifications_render_test.go` around lines 14 - 43, The test
TestRenderProjectRoleNotification currently checks only one happy path; refactor
it into a table-driven test that iterates multiple cases (e.g., normal values,
empty InviterName/RecipientName, names/roles with special characters) by
defining a slice of structs containing a name, projectRoleNotificationData
input, and expected snippets, then loop over cases and call
renderProjectRoleNotification for each, asserting subject/html/text contain or
don't contain the expected strings; update assertions to use t.Run(caseName,
func(t *testing.T) { ... }) and keep checks for HTML vs plain text, using the
existing renderProjectRoleNotification and projectRoleNotificationData symbols.
internal/domain/message.go (1)

9-30: ⚡ Quick win

Introduce a domain-owned DTO for email requests to maintain interface isolation.

The MessageBuilder interface exposes emailapi.SendEmailRequest directly, coupling the domain layer to an external service type. The codebase already follows this pattern elsewhere—external types like indexerTypes remain isolated in model implementations, not in domain interfaces. Create a domain-scoped request struct and map it to emailapi.SendEmailRequest in the infrastructure layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/domain/message.go` around lines 9 - 30, The MessageBuilder interface
currently leaks the external type emailapi.SendEmailRequest via the
SendEmailRequest method; introduce a domain-scoped DTO (e.g., type EmailRequest
struct { ... }) in the domain package and change
MessageBuilder.SendEmailRequest(ctx context.Context, req
emailapi.SendEmailRequest) error to use the new domain EmailRequest type
instead. Update all implementations of MessageBuilder (the concrete infra
message builder) to map/translate the domain EmailRequest into
emailapi.SendEmailRequest before calling the external email API, and adjust
imports/usages accordingly so only the infra layer depends on emailapi while the
domain stays isolated.
internal/infrastructure/nats/message.go (2)

210-217: ⚡ Quick win

Consider adding a timeout to the email service request.

The RequestMsgWithContext call respects the context deadline, but if the caller passes a context without a timeout, the request could block indefinitely. Consider wrapping the context with a timeout to bound the request duration.

⏱️ Proposed fix to add request timeout
+const emailRequestTimeout = 10 * time.Second
+
 // SendEmailRequest sends a request to the email service and waits for a reply.
 func (m *MessageBuilder) SendEmailRequest(ctx context.Context, req emailapi.SendEmailRequest) error {
 	data, err := json.Marshal(req)
 	if err != nil {
 		slog.ErrorContext(ctx, "error marshalling email request into JSON", constants.ErrKey, err)
 		return err
 	}
 
+	reqCtx, cancel := context.WithTimeout(ctx, emailRequestTimeout)
+	defer cancel()
+
-	reply, err := m.NatsConn.RequestMsgWithContext(ctx, &nats.Msg{
+	reply, err := m.NatsConn.RequestMsgWithContext(reqCtx, &nats.Msg{
 		Subject: emailapi.SendEmailSubject,
 		Data:    data,
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/infrastructure/nats/message.go` around lines 210 - 217, The
RequestMsgWithContext call in message.go can block if the incoming ctx has no
deadline; wrap the incoming ctx with a bounded timeout (e.g., via
context.WithTimeout) before calling m.NatsConn.RequestMsgWithContext in the
function that sends the email (the code around reply, err :=
m.NatsConn.RequestMsgWithContext(ctx, &nats.Msg{...})), pass the derived ctx
into RequestMsgWithContext, defer the cancel() immediately to avoid leaks, and
make the timeout duration a clear constant or configurable value (e.g.,
emailRequestTimeout) so tests/config can adjust it; keep the existing error
logging and return behavior unchanged.

219-224: 💤 Low value

Consider logging unmarshal failures for error responses.

If the email service returns malformed JSON in reply.Data, the unmarshal error is silently discarded and the request is treated as successful. While this aligns with the best-effort design, logging unmarshal failures would aid debugging.

📝 Optional improvement to log unmarshal failures
 	if len(reply.Data) > 0 {
 		var errResp emailapi.SendEmailErrorResponse
-		if jsonErr := json.Unmarshal(reply.Data, &errResp); jsonErr == nil && errResp.Error != "" {
-			return fmt.Errorf("email service error: %s", errResp.Error)
+		if jsonErr := json.Unmarshal(reply.Data, &errResp); jsonErr != nil {
+			slog.WarnContext(ctx, "failed to unmarshal email service response", constants.ErrKey, jsonErr)
+		} else if errResp.Error != "" {
+			return fmt.Errorf("email service error: %s", errResp.Error)
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/infrastructure/nats/message.go` around lines 219 - 224, When
handling reply.Data in the email response path, log json.Unmarshal failures so
malformed JSON from the email service isn't silently ignored: inside the
existing block that declares var errResp emailapi.SendEmailErrorResponse and
calls json.Unmarshal(reply.Data, &errResp), detect when jsonErr != nil and emit
a debug/warn log (including jsonErr and the raw reply.Data) via your logger
rather than discarding the error, while preserving the current behavior that
only returns an error when errResp.Error is set.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go.mod`:
- Line 94: The go.mod contains a machine-local replace for module
github.com/linuxfoundation/lfx-v2-email-service pointing to an absolute path;
remove this local replace before merge and restore a resolvable dependency:
either replace it with a published/tagged version or a pseudo-version (e.g.,
github.com/linuxfoundation/lfx-v2-email-service vX.Y.Z or
v0.0.0-yyyymmddhhmmss-commit), or move the local replace into go.work for
developer-only overrides; update any references to the module in imports if
needed and run `go mod tidy` to verify the module graph.

In `@internal/service/notifications_diff.go`:
- Around line 35-42: The loop that builds additions can append duplicates when
`new` contains repeated members; add a local seen map (e.g., map[string]bool)
keyed by `memberKey(u)` inside the same function (the diff logic around
memberKey/new/oldSet) and skip appending if seen[key] is true, setting
seen[key]=true when you first handle the user; this ensures `additions` (of type
roleAssignment) only gets one entry per unique member even if `new` contains
duplicates.

In `@internal/service/templates/project_role_notification.html`:
- Line 1: This new HTML template (project_role_notification.html) is missing the
required Linux Foundation copyright/license header; add the standard multi-line
license header at the very top of the file before the <!DOCTYPE html>
declaration so CI recognizes it (ensure the header text matches the project's
canonical Linux Foundation copyright/license block and preserves any required
year and copyright holder placeholders).

---

Outside diff comments:
In `@internal/service/templates/project_role_notification.txt`:
- Around line 1-11: Add the required license header to the top of the template
using a Go template comment so it is not rendered in outgoing emails; wrap the
full license text inside {{/* ... */}} and place it before any template content
(above the existing usages like {{.RecipientName}}, {{.InviterName}}, {{.Role}},
{{.ProjectName}}, {{.ProjectURL}}) to satisfy the CI license-header checks
without altering the visible email.

---

Nitpick comments:
In `@internal/domain/message.go`:
- Around line 9-30: The MessageBuilder interface currently leaks the external
type emailapi.SendEmailRequest via the SendEmailRequest method; introduce a
domain-scoped DTO (e.g., type EmailRequest struct { ... }) in the domain package
and change MessageBuilder.SendEmailRequest(ctx context.Context, req
emailapi.SendEmailRequest) error to use the new domain EmailRequest type
instead. Update all implementations of MessageBuilder (the concrete infra
message builder) to map/translate the domain EmailRequest into
emailapi.SendEmailRequest before calling the external email API, and adjust
imports/usages accordingly so only the infra layer depends on emailapi while the
domain stays isolated.

In `@internal/infrastructure/nats/message.go`:
- Around line 210-217: The RequestMsgWithContext call in message.go can block if
the incoming ctx has no deadline; wrap the incoming ctx with a bounded timeout
(e.g., via context.WithTimeout) before calling m.NatsConn.RequestMsgWithContext
in the function that sends the email (the code around reply, err :=
m.NatsConn.RequestMsgWithContext(ctx, &nats.Msg{...})), pass the derived ctx
into RequestMsgWithContext, defer the cancel() immediately to avoid leaks, and
make the timeout duration a clear constant or configurable value (e.g.,
emailRequestTimeout) so tests/config can adjust it; keep the existing error
logging and return behavior unchanged.
- Around line 219-224: When handling reply.Data in the email response path, log
json.Unmarshal failures so malformed JSON from the email service isn't silently
ignored: inside the existing block that declares var errResp
emailapi.SendEmailErrorResponse and calls json.Unmarshal(reply.Data, &errResp),
detect when jsonErr != nil and emit a debug/warn log (including jsonErr and the
raw reply.Data) via your logger rather than discarding the error, while
preserving the current behavior that only returns an error when errResp.Error is
set.

In `@internal/service/notifications_diff_test.go`:
- Around line 20-84: Add a new table-test entry in the tests slice that checks
deduplication of new members: create a case (e.g. name "duplicate new writers
deduped") where new is events.ProjectSettings{Writers: []events.UserInfo{alice,
alice}} and set wantLen: 1 and wantContains: []roleAssignment{{User: alice,
Role: "Writer"}}; this ensures the code that produces roleAssignment (referenced
by roleAssignment type and the logic comparing old vs new
ProjectSettings/Writers) returns only one assignment for duplicate entries.

In `@internal/service/notifications_render_test.go`:
- Around line 14-43: The test TestRenderProjectRoleNotification currently checks
only one happy path; refactor it into a table-driven test that iterates multiple
cases (e.g., normal values, empty InviterName/RecipientName, names/roles with
special characters) by defining a slice of structs containing a name,
projectRoleNotificationData input, and expected snippets, then loop over cases
and call renderProjectRoleNotification for each, asserting subject/html/text
contain or don't contain the expected strings; update assertions to use
t.Run(caseName, func(t *testing.T) { ... }) and keep checks for HTML vs plain
text, using the existing renderProjectRoleNotification and
projectRoleNotificationData symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1dd9951e-1dd4-40a7-a4cf-f3c116c135e7

📥 Commits

Reviewing files that changed from the base of the PR and between 6d00c5d and 6aa5616.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (22)
  • CLAUDE.md
  • cmd/project-api/main.go
  • go.mod
  • internal/domain/message.go
  • internal/domain/mock.go
  • internal/domain/models/message.go
  • internal/infrastructure/nats/message.go
  • internal/infrastructure/nats/message_test.go
  • internal/infrastructure/nats/mock.go
  • internal/service/converters.go
  • internal/service/converters_test.go
  • internal/service/notifications_diff.go
  • internal/service/notifications_diff_test.go
  • internal/service/notifications_handler.go
  • internal/service/notifications_handler_test.go
  • internal/service/notifications_render.go
  • internal/service/notifications_render_test.go
  • internal/service/project_operations.go
  • internal/service/project_service.go
  • internal/service/templates/project_role_notification.html
  • internal/service/templates/project_role_notification.txt
  • pkg/events/project.go
💤 Files with no reviewable changes (1)
  • internal/domain/models/message.go

Comment thread go.mod Outdated
Comment thread internal/service/notifications_diff.go Outdated
Comment thread internal/service/email/templates/project_role_notification.html
…ed pseudo-version

The local filesystem replace directive for lfx-v2-email-service breaks CI
since the path only exists on the developer's machine. Switch to a go module
pseudo-version pinned to the HEAD commit of the LFXV2-1768 branch so the
module proxy can fetch it in any environment.

  v0.0.0-20260512204955-693efd22ee37

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings May 12, 2026 22:03
Comment thread go.mod
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
go.mod (1)

14-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve license-compliance alert for the new email-service dependency before merge.

github.com/linuxfoundation/lfx-v2-email-service is still associated with GitHub license-compliance alerts in this PR history, so this is a merge blocker until the dependency is policy-allowed or metadata is fixed upstream.

#!/bin/bash
set -euo pipefail

# Verify whether the module is still in the dependency graph and lockfile.
rg -n "github.com/linuxfoundation/lfx-v2-email-service" go.mod go.sum || true

# If available in your environment, check current Dependabot/license alerts for the repo:
# gh api /repos/linuxfoundation/lfx-v2-project-service/dependabot/alerts \
#   -f state=open -f per_page=100 | jq '.[] | {number, security_advisory: .security_advisory.summary, dependency: .dependency.package.name}'

Based on learnings: Ensure all GitHub Actions CI/CD workflows (mega-linter, project-api-build, license-header-check) pass before merging PRs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 14, The go.mod entry for
github.com/linuxfoundation/lfx-v2-email-service is triggering a
license-compliance alert; either remove or replace that module with a
policy-approved version or update its upstream license metadata, then update
go.sum and vendor as needed. Locate the go.mod entry for
github.com/linuxfoundation/lfx-v2-email-service, change the dependency to an
allowed version (or remove it if unused), run `go mod tidy` and regenerate
go.sum, verify the module is no longer present via a search for
"github.com/linuxfoundation/lfx-v2-email-service", and re-run CI checks
(mega-linter, project-api-build, license-header-check) to confirm the alert is
resolved before merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@go.mod`:
- Line 14: The go.mod entry for github.com/linuxfoundation/lfx-v2-email-service
is triggering a license-compliance alert; either remove or replace that module
with a policy-approved version or update its upstream license metadata, then
update go.sum and vendor as needed. Locate the go.mod entry for
github.com/linuxfoundation/lfx-v2-email-service, change the dependency to an
allowed version (or remove it if unused), run `go mod tidy` and regenerate
go.sum, verify the module is no longer present via a search for
"github.com/linuxfoundation/lfx-v2-email-service", and re-run CI checks
(mega-linter, project-api-build, license-header-check) to confirm the alert is
resolved before merging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60396a9b-e226-4949-9645-5db3e8ee575d

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa5616 and 41d8fdd.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • cmd/project-api/main.go
  • go.mod
  • internal/service/notifications_handler.go
  • internal/service/notifications_handler_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/project-api/main.go
  • internal/service/notifications_handler.go
  • internal/service/notifications_handler_test.go

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Comment thread internal/service/notifications_handler.go Outdated
Comment thread internal/service/project_subscriber.go
Comment thread internal/infrastructure/nats/message.go
Comment thread internal/service/notifications_handler_test.go Outdated
Comment thread go.mod
andrest50 added 2 commits May 12, 2026 15:09
…-commit hook

- Add HTML comment license header to project_role_notification.html
- Add Go template comment license header to project_role_notification.txt
  ({{- /* ... */ -}} renders to nothing so it does not appear in sent emails)
- Expand Makefile license-check target to cover tracked .html and .txt files
  via git ls-files (excludes gitignored files like megalinter-reports/)
- Add make license-check to pre-commit hook alongside fmt, vet, and lint

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
CI/lint fixes:
- Downgrade go directive to 1.24.0 (CI golangci-lint runs Go 1.24.7 with
  GOTOOLCHAIN=local; requiring go 1.25 prevented package graph loading)
- Update lfx-v2-email-service pseudo-version to bfe7955 (also on go 1.24)
- Exclude templates/ from license-header-check (Go template comment syntax
  {{- /* ... */ -}} is not recognized by the external license checker; the
  copyright is present but the checker needs plain or HTML comment format)

PR review comments addressed:
- diffRole: add seenNew map to prevent duplicate emails when new settings
  contains repeated member entries
- Skip email send when recipient has no email address; log warning instead
- Normalize LFXSelfServeBaseURL trailing slash before building project URL
- Rename test case "malformed message" → "project load failure" (accurate)
- Add TestMessageBuilder_SendEmailRequest covering success, NATS error, and
  email service error-response paths

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings May 12, 2026 22:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

.github/workflows/license-header-check.yml:19

  • exclude_pattern was expanded to exclude templates, which means the CI license-header workflow will no longer enforce headers for the newly added email templates. If templates are intended to carry license headers (they appear to in this PR), consider removing templates from the exclusion so CI continues to validate them.
    uses: linuxfoundation/lfx-public-workflows/.github/workflows/license-header-check.yml@main
    with:
      copyright_line: "Copyright The Linux Foundation and each contributor to LFX."
      exclude_pattern: "gen|templates"

Comment thread Makefile
Comment thread cmd/project-api/main.go
Comment thread go.mod
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/service/notifications_diff_test.go (1)

54-61: ⚡ Quick win

Strengthen the multi-role case with explicit role assertions.

This case currently checks only count, so incorrect role/user mappings could still pass. Add wantContains entries for both expected assignments.

Proposed test tweak
 		{
 			name: "multiple roles added",
 			old:  events.ProjectSettings{},
 			new: events.ProjectSettings{
 				Writers:  []events.UserInfo{alice},
 				Auditors: []events.UserInfo{bob},
 			},
-			wantLen: 2,
+			wantLen: 2,
+			wantContains: []roleAssignment{
+				{User: alice, Role: "Writer"},
+				{User: bob, Role: "Auditor"},
+			},
 		},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/notifications_diff_test.go` around lines 54 - 61, The
"multiple roles added" table test only asserts wantLen but should also assert
the specific role→user mappings; update the test case (the entry with name
"multiple roles added" in notifications_diff_test.go) to include wantContains
(or similar expected-members field used by the table) with explicit entries for
the Writers assignment containing alice and the Auditors assignment containing
bob so the test verifies both count and correct role/user mapping for
events.ProjectSettings diffs.
internal/infrastructure/nats/message_test.go (1)

439-450: ⚡ Quick win

Assert the serialized email payload, not just the subject.

Right now these success cases can pass even if To/Subject/HTML/Text marshalling breaks. Validate msg.Data content in the matcher.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/infrastructure/nats/message_test.go` around lines 439 - 450, The
test's mock matcher for MockNATSConn.RequestMsgWithContext currently only checks
msg.Subject == emailapi.SendEmailSubject, which lets broken email marshalling
pass; update the matcher to also deserialize and assert the serialized email
payload in msg.Data contains the expected To, Subject, HTML and Text fields (or
directly compare to the expected JSON bytes) so the test fails if
To/Subject/HTML/Text marshalling changes; update both success cases' mockSetup
matchers to validate msg.Data content alongside the subject.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/service/notifications_handler_test.go`:
- Around line 120-143: The test currently sets expectations with
mockRepo.On("GetProjectBase", ...) and mockMsg.On("SendEmailRequest", ...) but
only verifies call counts; after invoking svc.HandleProjectSettingsUpdated(...)
add verification calls mockRepo.AssertExpectations(t) and
mockMsg.AssertExpectations(t) to ensure all mocked expectations (GetProjectBase
and SendEmailRequest) were satisfied; place these assertions immediately after
the handler invocation and before or after mockMsg.AssertNumberOfCalls to follow
the repository's test pattern.

---

Nitpick comments:
In `@internal/infrastructure/nats/message_test.go`:
- Around line 439-450: The test's mock matcher for
MockNATSConn.RequestMsgWithContext currently only checks msg.Subject ==
emailapi.SendEmailSubject, which lets broken email marshalling pass; update the
matcher to also deserialize and assert the serialized email payload in msg.Data
contains the expected To, Subject, HTML and Text fields (or directly compare to
the expected JSON bytes) so the test fails if To/Subject/HTML/Text marshalling
changes; update both success cases' mockSetup matchers to validate msg.Data
content alongside the subject.

In `@internal/service/notifications_diff_test.go`:
- Around line 54-61: The "multiple roles added" table test only asserts wantLen
but should also assert the specific role→user mappings; update the test case
(the entry with name "multiple roles added" in notifications_diff_test.go) to
include wantContains (or similar expected-members field used by the table) with
explicit entries for the Writers assignment containing alice and the Auditors
assignment containing bob so the test verifies both count and correct role/user
mapping for events.ProjectSettings diffs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56020bc1-2121-43ee-9c53-13ab325d1648

📥 Commits

Reviewing files that changed from the base of the PR and between 8129c73 and 5fe44f5.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • .github/workflows/license-header-check.yml
  • go.mod
  • internal/infrastructure/nats/message_test.go
  • internal/service/notifications_diff.go
  • internal/service/notifications_diff_test.go
  • internal/service/notifications_handler.go
  • internal/service/notifications_handler_test.go
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/license-header-check.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/service/notifications_diff.go
  • internal/service/notifications_handler.go

Comment thread internal/service/project_subscriber_test.go
andrest50 added 3 commits May 12, 2026 15:37
- notifications_handler.go → project_subscriber.go
  Rename makes the pattern explicit: event subscribers (fire-and-forget,
  no response) live here; future handlers like ProjectUpdated belong here too
- Fold notifications_diff.go into project_subscriber.go
  diffNewMembers/diffRole/memberKey are only called from the subscriber and
  don't warrant a separate file
- Merge the two test files into project_subscriber_test.go accordingly
- Update log prefixes from "notifications_handler:" to "project_subscriber:"

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
…ructure

Move packages to align internal/ with a strict three-layer layout:

  internal/log/        → internal/infrastructure/log/
  internal/middleware/ → internal/infrastructure/middleware/
  internal/service/notifications_render.go + templates/
                       → internal/service/email/ (new package)

The email package exports RenderProjectRoleNotification and
ProjectRoleNotificationData; adding a new email type means adding one
file to internal/service/email/ alongside its templates.

Update CLAUDE.md:
- Reflect new directory tree
- Bump Go version to 1.24+
- Add LFX_SELF_SERVE_BASE_URL to env vars table
- Clarify the two distinct NATS patterns (request/reply RPC vs
  event subscriptions) and which file each belongs in

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
- Accept both "staging" and "stg" for LFX_ENVIRONMENT staging URL
- Add AssertExpectations to project_subscriber_test table-driven cases
- Strengthen license-check Makefile target: add SPDX check, share git
  ls-files result, and exclude email templates (Go template comment
  syntax not recognized by external license checker)

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/service/project_subscriber.go (1)

30-31: ⚡ Quick win

Use infrastructure logging helpers instead of direct slog.WarnContext in internal layer code.

This file should route structured logging through the shared logging helpers for consistency with repository conventions.

As per coding guidelines: internal/**/*.go: Use structured logging helpers (AppendCtx, InitStructureLogConfig) from internal/infrastructure/log/ for consistent log output.

Also applies to: 41-42, 62-64, 83-85, 97-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/project_subscriber.go` around lines 30 - 31, Replace direct
slog.WarnContext calls in project_subscriber.go with the repository's structured
logging helpers: initialize or ensure the structured logger via
InitStructureLogConfig where necessary, then use AppendCtx(ctx) from
internal/infrastructure/log to emit the warning. For each occurrence (e.g., the
current slog.WarnContext(ctx, "project_subscriber: failed to unmarshal project
settings updated event", constants.ErrKey, err)), call the shared helper chain
(InitStructureLogConfig if needed, then log.AppendCtx(ctx).Warn(...)) and pass
the same message and structured fields (constants.ErrKey, err) so the output
matches current semantics but uses the shared logger. Ensure you update all
listed sites (around lines 30–31, 41–42, 62–64, 83–85, 97–99) to use AppendCtx
and InitStructureLogConfig instead of slog.WarnContext.
internal/service/project_subscriber_test.go (1)

162-233: ⚡ Quick win

Add a regression case for email-only → username+email identity transition.

TestDiffNewMembers currently misses the scenario where the same user appears as email-only in old and username+same-email in new; this should not be treated as a new addition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/project_subscriber_test.go` around lines 162 - 233, Add a
regression test case in the TestDiffNewMembers table that ensures an identity
transition from email-only to username+same-email is not treated as a new
member: in the tests slice (used by TestDiffNewMembers) add an entry where old
is events.ProjectSettings{Writers: []events.UserInfo{noUsername}} and new is
events.ProjectSettings{Writers: []events.UserInfo{alice}} (alice has the same
email as noUsername) and expect no additions (omit wantLen or set wantLen: 0);
reference the existing roleAssignment and Writers handling so the diff logic
that compares identities recognizes equal users and does not emit a new
roleAssignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/service/project_subscriber.go`:
- Around line 125-160: The current memberKey("memberKey") can produce different
keys for the same person when one record has only Email and another has
Username+Email; change memberKey so it produces a stable canonical key across
those cases (e.g., prefer Email when present and fall back to Username, or
construct a deterministic composite like "email:<email>|username:<username>"
when both exist) so diffRole's comparison logic (diffRole) will not treat the
same user as a new addition when the identity shape changes.

In `@Makefile`:
- Around line 157-164: The xargs calls used to build missing_copyright and
missing_spdx can run grep recursively when TRACKED is empty; update the xargs
invocations that consume $$TRACKED (the ones populating missing_copyright and
missing_spdx) to pass the -r/--no-run-if-empty flag so xargs does nothing on
empty input, keeping the grep -rL checks limited to the intended file list and
preventing an unintended recursive scan from '.'.

---

Nitpick comments:
In `@internal/service/project_subscriber_test.go`:
- Around line 162-233: Add a regression test case in the TestDiffNewMembers
table that ensures an identity transition from email-only to username+same-email
is not treated as a new member: in the tests slice (used by TestDiffNewMembers)
add an entry where old is events.ProjectSettings{Writers:
[]events.UserInfo{noUsername}} and new is events.ProjectSettings{Writers:
[]events.UserInfo{alice}} (alice has the same email as noUsername) and expect no
additions (omit wantLen or set wantLen: 0); reference the existing
roleAssignment and Writers handling so the diff logic that compares identities
recognizes equal users and does not emit a new roleAssignment.

In `@internal/service/project_subscriber.go`:
- Around line 30-31: Replace direct slog.WarnContext calls in
project_subscriber.go with the repository's structured logging helpers:
initialize or ensure the structured logger via InitStructureLogConfig where
necessary, then use AppendCtx(ctx) from internal/infrastructure/log to emit the
warning. For each occurrence (e.g., the current slog.WarnContext(ctx,
"project_subscriber: failed to unmarshal project settings updated event",
constants.ErrKey, err)), call the shared helper chain (InitStructureLogConfig if
needed, then log.AppendCtx(ctx).Warn(...)) and pass the same message and
structured fields (constants.ErrKey, err) so the output matches current
semantics but uses the shared logger. Ensure you update all listed sites (around
lines 30–31, 41–42, 62–64, 83–85, 97–99) to use AppendCtx and
InitStructureLogConfig instead of slog.WarnContext.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34b38a6e-5e11-4a28-9b2e-59d448289e31

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe44f5 and 05868bc.

📒 Files selected for processing (25)
  • CLAUDE.md
  • Makefile
  • cmd/project-api/main.go
  • internal/infrastructure/log/log.go
  • internal/infrastructure/log/log_test.go
  • internal/infrastructure/middleware/authorization.go
  • internal/infrastructure/middleware/authorization_test.go
  • internal/infrastructure/middleware/body_limit.go
  • internal/infrastructure/middleware/body_limit_test.go
  • internal/infrastructure/middleware/request_id.go
  • internal/infrastructure/middleware/request_id_test.go
  • internal/infrastructure/middleware/request_logger.go
  • internal/infrastructure/middleware/request_logger_test.go
  • internal/service/document_operations.go
  • internal/service/email/project_role_notification.go
  • internal/service/email/project_role_notification_test.go
  • internal/service/email/templates/project_role_notification.html
  • internal/service/email/templates/project_role_notification.txt
  • internal/service/folder_operations.go
  • internal/service/link_operations.go
  • internal/service/project_handlers.go
  • internal/service/project_operations.go
  • internal/service/project_subscriber.go
  • internal/service/project_subscriber_test.go
  • scripts/root-project-setup/main.go
✅ Files skipped from review due to trivial changes (7)
  • internal/service/document_operations.go
  • internal/infrastructure/middleware/request_logger.go
  • internal/service/email/project_role_notification_test.go
  • internal/service/email/templates/project_role_notification.html
  • internal/service/email/templates/project_role_notification.txt
  • scripts/root-project-setup/main.go
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/project-api/main.go
  • internal/service/project_operations.go

Comment thread internal/service/project_subscriber.go
Comment thread Makefile Outdated
…ures

License header check: exclude_pattern uses comma-separated values, not
pipe. "gen|templates" was treated as a single literal pattern (| was
escaped), so the gen/ directory was never excluded. Changed to
"gen,templates" so both exclusions apply correctly.

MegaLinter djlint (H030, H031, H006, H023):
- Add meta description and keywords tags to HTML email template
- Add width/height attributes to img tag
- Replace &copy; entity reference with literal © character

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings May 12, 2026 23:15
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 38 changed files in this pull request and generated 1 comment.

Comment thread Makefile Outdated
andrest50 added 2 commits May 12, 2026 16:33
Align with the templates used in itx-service-zoom (the canonical LFX
Self-Serve email design):
- Logo from S3 (lfx-logo-color.png) replacing the old lfx.linuxfoundation.org URL
- White background, Inter font, flat wrapper layout (no colored header band)
- #009AFF button color, 8px border-radius, fallback link below CTA
- Full LFX trademark footer matching other LFX Self-Serve emails

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Move all inline styles to CSS classes to satisfy djlint H021:
- greeting p: new .greeting class
- btn anchor: drop redundant inline color (already in .btn)
- footer link: .footer a rule

Also:
- Remove h1 title below greeting; go straight into body message
- Link goes to /projects/overview instead of project-specific slug URL
- Drop unused fmt import from project_subscriber.go

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings May 13, 2026 15:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 38 changed files in this pull request and generated 1 comment.

Comment thread internal/service/project_subscriber.go
- Replace single-key memberKey with multi-key memberKeys so a user whose
  identity record gains a username between snapshots is not treated as a
  new addition (email-only in old matched by shared email in new)
- Add test case covering the identity-shape-change scenario
- Fix Makefile license-check to check only the first 4 lines of each file
  (matching CI behaviour) and use a POSIX-safe pipeline instead of bash
  process substitution, which also prevents recursive scanning when the
  tracked file list is empty

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants