Monorepo for Tangled
tangled.org
1package pulls
2
3import (
4 "fmt"
5 "net/http"
6 "strconv"
7 "time"
8
9 "tangled.org/core/api/tangled"
10 "tangled.org/core/appview/db"
11 "tangled.org/core/appview/models"
12 "tangled.org/core/appview/pages"
13 "tangled.org/core/appview/reporesolver"
14 "tangled.org/core/tid"
15
16 comatproto "github.com/bluesky-social/indigo/api/atproto"
17 lexutil "github.com/bluesky-social/indigo/lex/util"
18 "github.com/go-chi/chi/v5"
19)
20
21func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) {
22 l := s.logger.With("handler", "PullComment")
23
24 user := s.oauth.GetMultiAccountUser(r)
25 if user != nil {
26 l = l.With("user", user.Did)
27 }
28
29 f, err := s.repoResolver.Resolve(r)
30 if err != nil {
31 l.Error("failed to get repo and knot", "err", err)
32 return
33 }
34
35 pull, ok := r.Context().Value("pull").(*models.Pull)
36 if !ok {
37 l.Error("failed to get pull")
38 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
39 return
40 }
41 l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid)
42
43 roundNumberStr := chi.URLParam(r, "round")
44 roundNumber, err := strconv.Atoi(roundNumberStr)
45 if err != nil || roundNumber >= len(pull.Submissions) {
46 http.Error(w, "bad round id", http.StatusBadRequest)
47 l.Error("failed to parse round id", "err", err, "round_number_str", roundNumberStr)
48 return
49 }
50
51 switch r.Method {
52 case http.MethodGet:
53 s.pages.PullNewCommentFragment(w, pages.PullNewCommentParams{
54 LoggedInUser: user,
55 RepoInfo: s.repoResolver.GetRepoInfo(r, user),
56 Pull: pull,
57 RoundNumber: roundNumber,
58 })
59 return
60 case http.MethodPost:
61 body := r.FormValue("body")
62 if body == "" {
63 s.pages.Notice(w, "pull", "Comment body is required")
64 return
65 }
66
67 mentions, references := s.mentionsResolver.Resolve(r.Context(), body)
68
69 // Start a transaction
70 tx, err := s.db.BeginTx(r.Context(), nil)
71 if err != nil {
72 l.Error("failed to start transaction", "err", err)
73 s.pages.Notice(w, "pull-comment", "Failed to create comment.")
74 return
75 }
76 defer tx.Rollback()
77
78 createdAt := time.Now().Format(time.RFC3339)
79
80 client, err := s.oauth.AuthorizedClient(r)
81 if err != nil {
82 l.Error("failed to get authorized client", "err", err)
83 s.pages.Notice(w, "pull-comment", "Failed to create comment.")
84 return
85 }
86 atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
87 Collection: tangled.RepoPullCommentNSID,
88 Repo: user.Did,
89 Rkey: tid.TID(),
90 Record: &lexutil.LexiconTypeDecoder{
91 Val: &tangled.RepoPullComment{
92 Pull: pull.AtUri().String(),
93 Body: body,
94 CreatedAt: createdAt,
95 },
96 },
97 })
98 if err != nil {
99 l.Error("failed to create pull comment", "err", err)
100 s.pages.Notice(w, "pull-comment", "Failed to create comment.")
101 return
102 }
103
104 comment := &models.PullComment{
105 OwnerDid: user.Did,
106 RepoDid: string(f.RepoDid),
107 PullId: pull.PullId,
108 Body: body,
109 CommentAt: atResp.Uri,
110 SubmissionId: pull.Submissions[roundNumber].ID,
111 Mentions: mentions,
112 References: references,
113 }
114
115 // Create the pull comment in the database with the commentAt field
116 commentId, err := db.NewPullComment(tx, comment)
117 if err != nil {
118 l.Error("failed to create pull comment in database", "err", err)
119 s.pages.Notice(w, "pull-comment", "Failed to create comment.")
120 return
121 }
122
123 // Commit the transaction
124 if err = tx.Commit(); err != nil {
125 l.Error("failed to commit transaction", "err", err)
126 s.pages.Notice(w, "pull-comment", "Failed to create comment.")
127 return
128 }
129
130 s.notifier.NewPullComment(r.Context(), comment, mentions)
131
132 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
133 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId))
134 return
135 }
136}