88use App \Notifications \IssueAssigned ;
99use Illuminate \Contracts \Queue \ShouldQueue ;
1010use Illuminate \Queue \InteractsWithQueue ;
11+ use Illuminate \Support \Facades \DB ;
1112use Illuminate \Support \Facades \Route ;
1213
1314/**
@@ -22,20 +23,43 @@ final class SendIssueAssignedNotification implements ShouldQueue
2223
2324 public function handle (IssueAssigneeChanged $ event ): void
2425 {
25- /** @var Issue|null $issue */
26- $ issue = Issue::query ()
27- ->select (['id ' , 'summary ' , 'project_id ' ])
28- ->find ($ event ->issueId );
29-
30- /** @var User|null $user */
31- $ user = User::query ()
32- ->select (['id ' , 'name ' , 'email ' ])
33- ->find ($ event ->newAssigneeId );
26+ // Self-assignment: don't notify the actor about their own action
27+ if ($ event ->actorId !== null && (string ) $ event ->actorId === (string ) $ event ->newAssigneeId ) {
28+ return ;
29+ }
3430
31+ $ issue = Issue::query ()->select (['id ' ,'summary ' ,'project_id ' ])->find ($ event ->issueId );
32+ $ user = User::query ()->select (['id ' ,'name ' ,'email ' ])->find ($ event ->newAssigneeId );
3533 if (! $ issue || ! $ user ) {
3634 return ;
3735 }
3836
37+ // ---- DEDUPE: same issue to same user, unread, very recent
38+ $ query = $ user ->notifications ()
39+ ->where ('type ' , IssueAssigned::class)
40+ ->whereNull ('read_at ' )
41+ ->where ('created_at ' , '>= ' , now ()->subMinutes (2 ));
42+
43+ // JSON filter by driver
44+ $ driver = DB ::getDriverName ();
45+ if ($ driver === 'pgsql ' ) {
46+ $ query ->whereRaw ("data->>'issue_id' = ? " , [(string ) $ issue ->getKey ()]);
47+ } elseif ($ driver === 'mysql ' ) {
48+ $ query ->where ('data->issue_id ' , (string ) $ issue ->getKey ());
49+ } else {
50+ // fallback: simple LIKE (SQLite/dev)
51+ $ needle = '"issue_id":" ' . $ issue ->getKey () . '" ' ;
52+ // Escape %, _, and \ for LIKE pattern
53+ $ escapedNeedle = str_replace (['\\' , '% ' , '_ ' ], ['\\\\' , '\\% ' , '\\_ ' ], $ needle );
54+ $ likePattern = '% ' . $ escapedNeedle . '% ' ;
55+ $ query ->whereRaw ("data LIKE ? ESCAPE ' \\' " , [$ likePattern ]);
56+ }
57+
58+ if ($ query ->exists ()) {
59+ return ; // skip duplicate
60+ }
61+ // ---- /DEDUPE
62+
3963 $ url = Route::has ('issues.show ' )
4064 ? route ('issues.show ' , ['project ' => $ issue ->project , 'issue ' => $ issue ])
4165 : url ('projects/ ' . $ issue ->project_id . '/issues/ ' . $ issue ->key );
0 commit comments