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 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
61 xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo)
62 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
63 l.Error("failed to call XRPC repo.log", "err", xrpcerr)
64 rp.pages.Error503(w)
65 return
66 }
67
68 var xrpcResp types.RepoLogResponse
69 if err := json.Unmarshal(xrpcBytes, &xrpcResp); err != nil {
70 l.Error("failed to decode XRPC response", "err", err)
71 rp.pages.Error503(w)
72 return
73 }
74
75 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
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, repo)
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.GetUser(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.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits)
120 if err != nil {
121 l.Error("failed to GetVerifiedObjectCommits", "err", err)
122 }
123
124 repoInfo := f.RepoInfo(user)
125
126 var shas []string
127 for _, c := range xrpcResp.Commits {
128 shas = append(shas, c.Hash.String())
129 }
130 pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas)
131 if err != nil {
132 l.Error("failed to getPipelineStatuses", "err", err)
133 // non-fatal
134 }
135
136 rp.pages.RepoLog(w, pages.RepoLogParams{
137 LoggedInUser: user,
138 TagMap: tagMap,
139 RepoInfo: repoInfo,
140 RepoLogResponse: xrpcResp,
141 EmailToDid: emailToDidMap,
142 VerifiedCommits: vc,
143 Pipelines: pipelines,
144 })
145}
146
147func (rp *Repo) Commit(w http.ResponseWriter, r *http.Request) {
148 l := rp.logger.With("handler", "RepoCommit")
149
150 f, err := rp.repoResolver.Resolve(r)
151 if err != nil {
152 l.Error("failed to fully resolve repo", "err", err)
153 return
154 }
155 ref := chi.URLParam(r, "ref")
156 ref, _ = url.PathUnescape(ref)
157
158 var diffOpts types.DiffOpts
159 if d := r.URL.Query().Get("diff"); d == "split" {
160 diffOpts.Split = true
161 }
162
163 if !plumbing.IsHash(ref) {
164 rp.pages.Error404(w)
165 return
166 }
167
168 scheme := "http"
169 if !rp.config.Core.Dev {
170 scheme = "https"
171 }
172 host := fmt.Sprintf("%s://%s", scheme, f.Knot)
173 xrpcc := &indigoxrpc.Client{
174 Host: host,
175 }
176
177 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
178 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo)
179 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
180 l.Error("failed to call XRPC repo.diff", "err", xrpcerr)
181 rp.pages.Error503(w)
182 return
183 }
184
185 var result types.RepoCommitResponse
186 if err := json.Unmarshal(xrpcBytes, &result); err != nil {
187 l.Error("failed to decode XRPC response", "err", err)
188 rp.pages.Error503(w)
189 return
190 }
191
192 emailToDidMap, err := db.GetEmailToDid(rp.db, []string{result.Diff.Commit.Committer.Email, result.Diff.Commit.Author.Email}, true)
193 if err != nil {
194 l.Error("failed to get email to did mapping", "err", err)
195 }
196
197 vc, err := commitverify.GetVerifiedCommits(rp.db, emailToDidMap, []types.NiceDiff{*result.Diff})
198 if err != nil {
199 l.Error("failed to GetVerifiedCommits", "err", err)
200 }
201
202 user := rp.oauth.GetUser(r)
203 repoInfo := f.RepoInfo(user)
204 pipelines, err := getPipelineStatuses(rp.db, repoInfo, []string{result.Diff.Commit.This})
205 if err != nil {
206 l.Error("failed to getPipelineStatuses", "err", err)
207 // non-fatal
208 }
209 var pipeline *models.Pipeline
210 if p, ok := pipelines[result.Diff.Commit.This]; ok {
211 pipeline = &p
212 }
213
214 rp.pages.RepoCommit(w, pages.RepoCommitParams{
215 LoggedInUser: user,
216 RepoInfo: f.RepoInfo(user),
217 RepoCommitResponse: result,
218 EmailToDid: emailToDidMap,
219 VerifiedCommit: vc,
220 Pipeline: pipeline,
221 DiffOpts: diffOpts,
222 })
223}