-
Notifications
You must be signed in to change notification settings - Fork 896
feat(events): per-resource subscription identity + Match hook + mail #1185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
liuxinyanglxy
wants to merge
24
commits into
main
Choose a base branch
from
feat/event-subscription-id
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
a3f382c
feat(event): add SubscriptionKey/NormalizeParams/Match fields; change…
liuxinyanglxy 790d621
feat(event): add ComputeSubscriptionID for per-resource subscription …
liuxinyanglxy 66a7f1d
feat(event/protocol): add SubscriptionID to Hello/PreShutdownCheck/Co…
liuxinyanglxy 4da3e2b
feat(event/bus): store SubscriptionID on Conn with EventKey fallback
liuxinyanglxy 7dcd0ce
feat(event/bus): key Hub dedup by SubscriptionID; add SubCount accessor
liuxinyanglxy 6ee4ba0
test(event/bus): cover handleHello legacy vs modern SubscriptionID paths
liuxinyanglxy 66e566d
feat(event/consume): wire NormalizeParams + ComputeSubscriptionID int…
liuxinyanglxy fe4622d
feat(event/consume): plumb SubscriptionID through checkLastForKey/con…
liuxinyanglxy 279c9f5
feat(event/consume): surface cleanup errors as WARN with idempotency …
liuxinyanglxy d98ff47
feat(event/consume): add sync Match filter before Process
liuxinyanglxy 224b928
feat(cmd/event): render SubscriptionKey marker in schema output
liuxinyanglxy 37bef6f
feat(cmd/event): render SubscriptionID column in status table
liuxinyanglxy 5cfcf54
feat(events/mail): add MailReceivedPayload union schema
liuxinyanglxy 3688c1c
feat(events/mail): add normalizeMailParams to resolve 'me' alias
liuxinyanglxy a5f6ae1
feat(events/mail): add matchMailbox sync filter
liuxinyanglxy c7bb890
feat(events/mail): add processMailEvent for filter + format enrichment
liuxinyanglxy 08e90f7
feat(events/mail): wire folders/labels/msg-format params + new framew…
liuxinyanglxy becaac0
docs(skills): document subscription identity + cleanup error semantics
liuxinyanglxy 31c57a6
fix(events/mail): use /user_mailboxes/me/profile with primary_email_a…
liuxinyanglxy c2741c5
docs(skills): converge mail event docs into lark-event (IM-style)
liuxinyanglxy cbbc84c
fix(events/mail): decode subscriber as structured user_ids object
liuxinyanglxy 95f5b80
fix(events): adapt minutes/vc PreConsume cleanup to func() error
liuxinyanglxy a42f734
chore: gofmt event test files
liuxinyanglxy 654f656
fix(events): address CodeRabbit review on PR
liuxinyanglxy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package mail | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
|
|
||
| "github.com/larksuite/cli/internal/event" | ||
| ) | ||
|
|
||
| // matchMailbox compares the V2-envelope payload's event.mail_address against | ||
| // the normalized params.mailbox. Drops events whose mail_address doesn't match. | ||
| // | ||
| // Fail-open policy: if params.mailbox is empty (no filter), the payload can't | ||
| // be parsed, or the payload omits/moves event.mail_address (defensive — | ||
| // upstream schema may evolve), accept the event rather than silently dropping | ||
| // legitimate traffic. Only a present-but-mismatched mail_address drops. | ||
| // | ||
| // IMPORTANT: caller must ensure params.mailbox is already normalized to a real | ||
| // email (not "me"). normalizeMailParams handles this. | ||
| func matchMailbox(raw *event.RawEvent, params map[string]string) bool { | ||
| target := params["mailbox"] | ||
| if target == "" { | ||
| return true | ||
| } | ||
| var env struct { | ||
| Event struct { | ||
| MailAddress string `json:"mail_address"` | ||
| } `json:"event"` | ||
| } | ||
| if err := json.Unmarshal(raw.Payload, &env); err != nil { | ||
| return true // fail-open on unparseable payload | ||
| } | ||
| if env.Event.MailAddress == "" { | ||
| return true // fail-open on shape drift (field absent/moved); let Process decide | ||
| } | ||
| return env.Event.MailAddress == target | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package mail | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/larksuite/cli/internal/event" | ||
| ) | ||
|
|
||
| func TestMatchMailbox(t *testing.T) { | ||
| makeV2Envelope := func(mailAddr string) json.RawMessage { | ||
| return json.RawMessage(`{"schema":"2.0","header":{},"event":{"mail_address":"` + mailAddr + `"}}`) | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| payload json.RawMessage | ||
| params map[string]string | ||
| want bool | ||
| }{ | ||
| { | ||
| name: "exact match", | ||
| payload: makeV2Envelope("alice@example.com"), | ||
| params: map[string]string{"mailbox": "alice@example.com"}, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "mismatch drops", | ||
| payload: makeV2Envelope("bob@example.com"), | ||
| params: map[string]string{"mailbox": "alice@example.com"}, | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "empty params accepts all (fail-open: no filter)", | ||
| payload: makeV2Envelope("anything@example.com"), | ||
| params: map[string]string{}, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "malformed payload fail-open", | ||
| payload: json.RawMessage(`not json`), | ||
| params: map[string]string{"mailbox": "alice@example.com"}, | ||
| want: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| raw := &event.RawEvent{Payload: tt.payload} | ||
| if got := matchMailbox(raw, tt.params); got != tt.want { | ||
| t.Errorf("got %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // Compile-time: matchMailbox must match the Match signature. | ||
| var _ func(*event.RawEvent, map[string]string) bool = matchMailbox |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package mail | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/larksuite/cli/internal/event" | ||
| ) | ||
|
|
||
| // normalizeMailParams resolves the mailbox alias "me" (or empty) into the | ||
| // user's real primary email so fingerprint / Match / Process all see the | ||
| // canonical value. | ||
| // | ||
| // API: GET /open-apis/mail/v1/user_mailboxes/me/profile — returns | ||
| // {data:{primary_email_address:"..."}}. Mirrors the same OAPI call that | ||
| // shortcuts/mail/helpers.go::fetchMailboxPrimaryEmail uses, so both code | ||
| // paths (event consume and mail +watch) resolve "me" identically. | ||
| func normalizeMailParams(ctx context.Context, rt event.APIClient, params map[string]string) error { | ||
| mbox := strings.TrimSpace(params["mailbox"]) | ||
| if mbox == "" || mbox == "me" { | ||
| data, err := rt.CallAPI(ctx, "GET", "/open-apis/mail/v1/user_mailboxes/me/profile", nil) | ||
| if err != nil { | ||
| return fmt.Errorf("resolve mailbox 'me': %w", err) | ||
| } | ||
| email, err := extractPrimaryEmail(data) | ||
| if err != nil { | ||
| return fmt.Errorf("decode user_mailboxes/me/profile response: %w", err) | ||
| } | ||
| if email == "" { | ||
| return fmt.Errorf("user_mailboxes/me/profile returned empty primary_email_address") | ||
| } | ||
| params["mailbox"] = email | ||
| return nil | ||
| } | ||
| params["mailbox"] = mbox | ||
| return nil | ||
| } | ||
|
|
||
| // extractPrimaryEmail pulls primary_email_address out of the profile response. | ||
| // Tolerates both top-level shape (test fixtures) and the canonical nested | ||
| // `data` wrapper used by production responses. | ||
| func extractPrimaryEmail(raw json.RawMessage) (string, error) { | ||
| var asTop struct { | ||
| PrimaryEmailAddress string `json:"primary_email_address"` | ||
| Data struct { | ||
| PrimaryEmailAddress string `json:"primary_email_address"` | ||
| } `json:"data"` | ||
| } | ||
| if err := json.Unmarshal(raw, &asTop); err != nil { | ||
| return "", err | ||
| } | ||
| if asTop.PrimaryEmailAddress != "" { | ||
| return asTop.PrimaryEmailAddress, nil | ||
| } | ||
| return asTop.Data.PrimaryEmailAddress, nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.