Monorepo for Tangled tangled.org
4

Configure Feed

Select the types of activity you want to include in your feed.

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