Skip to content

feat(notification): include task/doc title in email event payload - #553

Merged
pikann merged 3 commits into
Paca-AI:masterfrom
vhervatin:feat/notification-entity-title
Oct 7, 2026
Merged

pikann merged 3 commits into
Paca-AI:masterfrom
vhervatin:feat/notification-entity-title

Conversation

@vhervatin

@vhervatin vhervatin commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Why

The notification.{assigned,mentioned,doc_mentioned,task_description_mentioned} events published to plugins carry only recipient_*, actor_name and link_url. A subscribing email plugin (the first-party com.paca.smtp) therefore can't name the task/document the notification is about — the email can only say "A task was assigned to you" with a bare link, while the in-app notification already shows the task title. This closes that gap so the email can match.

What

  • Add an entity_title field to the notification event payload (the task title for assigned/mentioned/task_description_mentioned, the document title for doc_mentioned).
  • Resolve it best-effort via a new, optional Svc.WithTitleLookup(taskRepo, docRepo). Both lookups are optional: when a repo is nil or the entity can't be loaded, entity_title is simply empty and behaviour is identical to before — so a subscribing plugin must tolerate an empty value.
  • For the mention topics (whose mentioned-user IDs come from client-supplied content), the title is resolved after the existing project-membership check, preserving the current anti-leak guarantee. The title is resolved once per call (e.g. once per comment, not once per mentioned user).
  • Minimal local lookup interfaces (taskLookup/docLookup), matching the existing userLookup/memberLookup pattern; the real repositories satisfy them. Wired in bootstrap.

Testing

  • go build ./..., go vet ./... clean.
  • New unit tests in notification/service_test.go cover: the title is resolved for assignments; resolved once for a multi-mention comment; and resolved only after the membership check for the client-supplied mention topics (zero lookups for a non-member).
  • Companion plugin change that consumes the field: feat: show the task/document title in notification emails paca-plugin-smtp#4 (works against this and degrades gracefully against an older core).

🤖 Generated with Claude Code

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Minor suggestions only.

Reviewed changes

  • entity_title on notification plugin events — adds a title field to the notification.{assigned,mentioned,doc_mentioned,task_description_mentioned} payloads, resolved via a new optional Svc.WithTitleLookup(taskRepo, docRepo); empty when no lookup is configured or the entity can't be loaded, so a subscribing plugin must tolerate an empty value.
  • Best-effort title helpers — taskTitle/docTitle return "" on a nil repo or lookup error; the mention paths resolve the title once per call and only after the project-membership check for the client-supplied doc/task-description mentions.
  • Bootstrap wiring + tests — WithTitleLookup(taskRepo, docRepo) wired in bootstrap; new unit tests assert the lookup runs (once for multi-mention comments, zero for a non-member). go test ./internal/service/notification/... passes.

The anti-leak ordering is preserved: for NotifyDocMentioned/NotifyTaskDescriptionMentioned the title is resolved only after FindMemberByUserProject, and no event is published for a non-member. Adding a JSON key is backward compatible for subscribers.

ℹ️ Nitpicks

  • The new tests assert lookup counts but never that entity_title actually appears in the published payload, so they would still pass if the field were dropped from publishNotificationEvent or wired to the wrong helper. Capturing the payload is awkward here because messaging.Publisher is a concrete Redis-backed struct and the file runs with a nil publisher, so this is understandable — but it means the feature's output is only covered indirectly.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread services/api/internal/service/notification/service.go
@vhervatin

Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed both points in eaad6534:

  • Full-entity read for a title (perf): added dedicated FindTaskTitleByID / FindDocumentTitleByID (SELECT title …) and switched the taskLookup/docLookup interfaces to them, so resolving entity_title no longer loads the full task row (description JSONB + the assignee query) or full document content on the synchronous comment/doc-update path.
  • Indirect test coverage (nitpick): publisher is now a small eventPublisher interface, so a new test (TestNotifyAssigned_PublishesEntityTitle) captures the published payload and asserts entity_title carries the resolved title — not just that the lookup ran.

go build, go vet and go test ./internal/service/notification/... pass.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Title-only lookups — taskLookup/docLookup now resolve entity_title through the new FindTaskTitleByID/FindDocumentTitleByID repository methods instead of loading the full task/document row, replacing the eager full-entity reads the prior review flagged.
  • Fakeable publisher — a minimal eventPublisher interface (Append + Publish) replaces the concrete *messaging.Publisher in New, so a test can capture what is published; *messaging.Publisher still satisfies it.
  • Payload assertion test — TestNotifyAssigned_PublishesEntityTitle captures the Append payload and asserts entity_title, directly covering the previously count-only gap.

go build ./..., go vet, and go test ./internal/service/notification/... ./internal/repository/postgres/... all pass.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pikann pikann left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! Please fix the failing lint check before merging.
Thank you for your contribution! 🚀

vhervatin and others added 3 commits October 7, 2026 09:18
The notification.{assigned,mentioned,doc_mentioned,
task_description_mentioned} events published to plugins carried only
actor_name and link_url, so a subscribing email plugin had no way to
name the task/document — the email could only say "a task was assigned
to you" with a bare link.

Add an entity_title field to the payload, resolved best-effort via a new
WithTitleLookup(taskRepo, docRepo) option: when no lookup is configured
or the entity can't be loaded, the title is simply empty and behaviour is
identical to before, so a subscribing plugin must tolerate an empty
entity_title. For the mention topics (whose IDs come from client-supplied
content) the title is only resolved after the existing project-membership
check, preserving the current anti-leak guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses the PR review:
- entity_title now resolves via dedicated FindTaskTitleByID /
  FindDocumentTitleByID (SELECT title) instead of FindTaskByID /
  FindDocumentByID, which pulled the full row (task description JSONB +
  a second assignee query; full document content) just to read a title
  on the synchronous comment/doc-update path.
- The publisher is now an eventPublisher interface so a unit test can
  capture the published payload and assert entity_title carries the
  resolved title — not only that the lookup ran.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The repo-wide `golangci-lint run ./...` (api-pr-ci's Lint job) fails on
`net.Listen` in email_test.go, added by 27b1029 (the GHSA-f2mf-mg99-22w5
netguard fix) — a pre-existing violation unrelated to this PR's change,
but it turns the Lint check red on every PR built against current master.
Switch to (*net.ListenConfig).Listen with context, exactly as the noctx
linter prescribes, so the Lint job passes. `golangci-lint run ./...` is
now clean (0 issues).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vhervatin
vhervatin force-pushed the feat/notification-entity-title branch from eaad653 to 88bca4f Compare October 7, 2026 12:21
@vhervatin

Copy link
Copy Markdown
Contributor Author

Thanks @pikann for the review and for merging the plugin side! 🙏

Fixed the failing Lint check: I rebased this branch onto current master and the noctx failure was net.Listen in internal/platform/plugin/email_test.go:12, added by 27b1029 (the GHSA-f2mf-mg99-22w5 netguard fix) — a pre-existing violation unrelated to this PR, but golangci-lint run ./... runs repo-wide so it turns the Lint check red on any PR built against master. Switched it to (*net.ListenConfig).Listen(ctx, …) exactly as the linter prescribes (commit 88bca4f).

Verified locally: golangci-lint run --timeout=5m ./... → 0 issues, and go build/vet/test ./internal/platform/plugin/... ./internal/service/notification/... all pass.

The branch now needs a maintainer to re-approve the workflow run so CI re-runs.

@pikann
pikann merged commit a45ff54 into Paca-AI:master Oct 7, 2026
6 checks passed
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