Go · 3627 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package issues
4
5 import (
6 "context"
7 "errors"
8 "strconv"
9
10 "github.com/jackc/pgx/v5"
11 "github.com/jackc/pgx/v5/pgxpool"
12
13 issuesdb "github.com/tenseleyFlow/shithub/internal/issues/sqlc"
14 "github.com/tenseleyFlow/shithub/internal/markdown"
15 "github.com/tenseleyFlow/shithub/internal/notif"
16 )
17
18 // emitIssueEventTx emits a domain event for an issue/PR mutation
19 // inside the orchestrator's tx. Centralized so every call site
20 // records the same shape (kind, source, repo, mentions).
21 func emitIssueEventTx(
22 ctx context.Context,
23 tx pgx.Tx,
24 kind string,
25 issue issuesdb.Issue,
26 actorUserID int64,
27 repoIsPublic bool,
28 mentions []int64,
29 ) error {
30 return notif.EmitTx(ctx, tx, notif.Event{
31 ActorUserID: actorUserID,
32 Kind: kind,
33 RepoID: issue.RepoID,
34 SourceKind: "issue",
35 SourceID: issue.ID,
36 Public: repoIsPublic,
37 Mentions: mentions,
38 Extra: map[string]any{
39 "issue_number": issue.Number,
40 "issue_title": issue.Title,
41 },
42 })
43 }
44
45 // emitCommentEventTx emits a comment-create event. Carries the
46 // underlying issue number/title so the inbox can render a useful
47 // summary without an extra join.
48 func emitCommentEventTx(
49 ctx context.Context,
50 tx pgx.Tx,
51 kind string,
52 issue issuesdb.Issue,
53 commentID int64,
54 actorUserID int64,
55 repoIsPublic bool,
56 mentions []int64,
57 ) error {
58 return notif.EmitTx(ctx, tx, notif.Event{
59 ActorUserID: actorUserID,
60 Kind: kind,
61 RepoID: issue.RepoID,
62 SourceKind: "issue",
63 SourceID: issue.ID,
64 Public: repoIsPublic,
65 Mentions: mentions,
66 Extra: map[string]any{
67 "issue_number": issue.Number,
68 "issue_title": issue.Title,
69 "comment_id": strconv.FormatInt(commentID, 10),
70 },
71 })
72 }
73
74 // emitAssignmentEventTx fires when a user is added as an assignee.
75 // One event per assignee — the fan-out worker treats each as a
76 // separate "this user has been assigned" notification with reason
77 // `assignment`.
78 func emitAssignmentEventTx(
79 ctx context.Context,
80 tx pgx.Tx,
81 issue issuesdb.Issue,
82 actorUserID, assigneeUserID int64,
83 repoIsPublic bool,
84 ) error {
85 return notif.EmitTx(ctx, tx, notif.Event{
86 ActorUserID: actorUserID,
87 Kind: "issue_assigned",
88 RepoID: issue.RepoID,
89 SourceKind: "issue",
90 SourceID: issue.ID,
91 Public: repoIsPublic,
92 // `assignee_user_id` lets the fan-out worker route the
93 // notification to the assignee even if they're not yet a
94 // thread subscriber.
95 Extra: map[string]any{
96 "issue_number": issue.Number,
97 "issue_title": issue.Title,
98 "assignee_user_id": assigneeUserID,
99 },
100 })
101 }
102
103 // repoVisibilityPublic resolves the repo's visibility (public/private)
104 // using the pool. Best-effort: errors collapse to false (private),
105 // which is the safe default for the public-feed flag on the event row.
106 func repoVisibilityPublic(ctx context.Context, db pgxRow, repoID int64) (bool, error) {
107 var vis string
108 err := db.QueryRow(
109 ctx,
110 `SELECT visibility::text FROM repos WHERE id = $1`,
111 repoID,
112 ).Scan(&vis)
113 if err != nil {
114 if errors.Is(err, pgx.ErrNoRows) {
115 return false, nil
116 }
117 return false, err
118 }
119 return vis == "public", nil
120 }
121
122 // pgxRow is the minimum surface emit helpers need from a tx or pool.
123 type pgxRow interface {
124 QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
125 }
126
127 // mentionUserIDs is the call-site ergonomic wrapper around
128 // notif.ResolveMentionUserIDs. Lives here so issues.go's call site
129 // stays a one-liner.
130 func mentionUserIDs(ctx context.Context, pool *pgxpool.Pool, ms []markdown.Mention) []int64 {
131 return notif.ResolveMentionUserIDs(ctx, pool, ms)
132 }
133