Monorepo for Tangled
1package repo
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "net/url"
8 "strconv"
9
10 "tangled.org/core/api/tangled"
11 "tangled.org/core/appview/commitverify"
12 "tangled.org/core/appview/db"
13 "tangled.org/core/appview/models"
14 "tangled.org/core/appview/pages"
15 xrpcclient "tangled.org/core/appview/xrpcclient"
16 "tangled.org/core/types"
17
18 indigoxrpc "github.com/bluesky-social/indigo/xrpc"
19 "github.com/go-chi/chi/v5"
20 "github.com/go-git/go-git/v5/plumbing"
21)
22
23func (rp *Repo) Log(w http.ResponseWriter, r *http.Request) {
24 l := rp.logger.With("handler", "RepoLog")
25
26 f, err := rp.repoResolver.Resolve(r)
27 if err != nil {
28 l.Error("failed to fully resolve repo", "err", err)
29 return
30 }
31
32 page := 1
33 if r.URL.Query().Get("page") != "" {
34 page, err = strconv.Atoi(r.URL.Query().Get("page"))
35 if err != nil {
36 page = 1
37 }
38 }
39
40 ref := chi.URLParam(r, "ref")
41 ref, _ = url.PathUnescape(ref)
42
43 scheme := "http"
44 if !rp.config.Core.Dev {
45 scheme = "https"
46 }
47 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
48 xrpcc := &indigoxrpc.Client{
49 Host: host,
50 }
51
52 limit := int64(60)
53 cursor := ""
54 if page > 1 {
55 // Convert page number to cursor (offset)
56 offset := (page - 1) * int(limit)
57 cursor = strconv.Itoa(offset)
58 }
59
60 xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, f.RepoIdentifier())
61 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
62 l.Error("failed to call XRPC repo.log", "err", xrpcerr)
63 rp.pages.Error503(w)
64 return
65 }
66
67 var xrpcResp types.RepoLogResponse
68 if err := json.Unmarshal(xrpcBytes, &xrpcResp); err != nil {
69 l.Error("failed to decode XRPC response", "err", err)
70 rp.pages.Error503(w)
71 return
72 }
73
74 repoId := f.RepoIdentifier()
75 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repoId)
76 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
77 l.Error("failed to call XRPC repo.tags", "err", xrpcerr)
78 rp.pages.Error503(w)
79 return
80 }
81
82 tagMap := make(map[string][]string)
83 if tagBytes != nil {
84 var tagResp types.RepoTagsResponse
85 if err := json.Unmarshal(tagBytes, &tagResp); err == nil {
86 for _, tag := range tagResp.Tags {
87 hash := tag.Hash
88 if tag.Tag != nil {
89 hash = tag.Tag.Target.String()
90 }
91 tagMap[hash] = append(tagMap[hash], tag.Name)
92 }
93 }
94 }
95
96 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repoId)
97 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
98 l.Error("failed to call XRPC repo.branches", "err", xrpcerr)
99 rp.pages.Error503(w)
100 return
101 }
102
103 if branchBytes != nil {
104 var branchResp types.RepoBranchesResponse
105 if err := json.Unmarshal(branchBytes, &branchResp); err == nil {
106 for _, branch := range branchResp.Branches {
107 tagMap[branch.Hash] = append(tagMap[branch.Hash], branch.Name)
108 }
109 }
110 }
111
112 user := rp.oauth.GetMultiAccountUser(r)
113
114 emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(xrpcResp.Commits), true)
115 if err != nil {
116 l.Error("failed to fetch email to did mapping", "err", err)
117 }
118
119 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, xrpcResp.Commits)
120 if err != nil {
121 l.Error("failed to GetVerifiedObjectCommits", "err", err)
122 }
123
124 var shas []string
125 for _, c := range xrpcResp.Commits {
126 shas = append(shas, c.Hash.String())
127 }
128 pipelines, err := getPipelineStatuses(rp.db, f, shas)
129 if err != nil {
130 l.Error("failed to getPipelineStatuses", "err", err)
131 // non-fatal
132 }
133
134 rp.pages.RepoLog(w, pages.RepoLogParams{
135 LoggedInUser: user,
136 TagMap: tagMap,
137 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
138 RepoLogResponse: xrpcResp,
139 EmailToDid: emailToDidMap,
140 VerifiedCommits: vc,
141 Pipelines: pipelines,
142 })
143}
144
145func (rp *Repo) Commit(w http.ResponseWriter, r *http.Request) {
146 l := rp.logger.With("handler", "RepoCommit")
147
148 f, err := rp.repoResolver.Resolve(r)
149 if err != nil {
150 l.Error("failed to fully resolve repo", "err", err)
151 return
152 }
153 ref := chi.URLParam(r, "ref")
154 ref, _ = url.PathUnescape(ref)
155
156 var diffOpts types.DiffOpts
157 if d := r.URL.Query().Get("diff"); d == "split" {
158 diffOpts.Split = true
159 }
160
161 if !plumbing.IsHash(ref) {
162 rp.pages.Error404(w)
163 return
164 }
165
166 scheme := "http"
167 if !rp.config.Core.Dev {
168 scheme = "https"
169 }
170 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
171 xrpcc := &indigoxrpc.Client{
172 Host: host,
173 }
174
175 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, f.RepoIdentifier())
176 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
177 l.Error("failed to call XRPC repo.diff", "err", xrpcerr)
178 rp.pages.Error503(w)
179 return
180 }
181
182 var result types.RepoCommitResponse
183 if err := json.Unmarshal(xrpcBytes, &result); err != nil {
184 l.Error("failed to decode XRPC response", "err", err)
185 rp.pages.Error503(w)
186 return
187 }
188
189 emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Committer.Email, result.Diff.Commit.Author.Email}, true)
190 if err != nil {
191 l.Error("failed to get email to did mapping", "err", err)
192 }
193
194 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.Commit{result.Diff.Commit})
195 if err != nil {
196 l.Error("failed to GetVerifiedCommits", "err", err)
197 }
198
199 user := rp.oauth.GetMultiAccountUser(r)
200 pipelines, err := getPipelineStatuses(rp.db, f, []string{result.Diff.Commit.This})
201 if err != nil {
202 l.Error("failed to getPipelineStatuses", "err", err)
203 // non-fatal
204 }
205 var pipeline *models.Pipeline
206 if p, ok := pipelines[result.Diff.Commit.This]; ok {
207 pipeline = &p
208 }
209
210 rp.pages.RepoCommit(w, pages.RepoCommitParams{
211 LoggedInUser: user,
212 RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
213 RepoCommitResponse: result,
214 EmailToDid: emailToDidMap,
215 VerifiedCommit: vc,
216 Pipeline: pipeline,
217 DiffOpts: diffOpts,
218 })
219}