Monorepo for Tangled tangled.org

appview: implement some basic pull request handlers

Changed files
+501 -52
appview
db
pages
templates
repo
state
cmd
+4 -1
appview/db/db.go
··· 109 109 repo_at text not null, 110 110 pull_id integer not null, 111 111 title text not null, 112 + body text not null, 112 113 patch text, 113 - patch_at text not null, 114 + pull_at text, 115 + rkey text not null, 116 + target_branch text not null, 114 117 open integer not null default 1, 115 118 created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 116 119 unique(repo_at, pull_id),
+46 -43
appview/db/pulls.go
··· 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 8 ) 9 9 10 - type Pulls struct { 11 - ID int `json:"id"` 12 - OwnerDid string `json:"owner_did"` 13 - RepoAt string `json:"repo_at"` 14 - PullId int `json:"pull_id"` 15 - Title string `json:"title"` 16 - Patch string `json:"patch,omitempty"` 17 - PatchAt string `json:"patch_at"` 18 - Open int `json:"open"` 19 - Created time.Time `json:"created"` 10 + type Pull struct { 11 + ID int 12 + OwnerDid string 13 + RepoAt syntax.ATURI 14 + PullAt syntax.ATURI 15 + TargetBranch string 16 + Patch string 17 + PullId int 18 + Title string 19 + Body string 20 + Open int 21 + Created time.Time 22 + Rkey string 20 23 } 21 24 22 - type PullComments struct { 23 - ID int `json:"id"` 24 - OwnerDid string `json:"owner_did"` 25 - PullId int `json:"pull_id"` 26 - RepoAt string `json:"repo_at"` 27 - CommentId int `json:"comment_id"` 28 - CommentAt string `json:"comment_at"` 29 - Body string `json:"body"` 30 - Created time.Time `json:"created"` 25 + type PullComment struct { 26 + ID int 27 + OwnerDid string 28 + PullId int 29 + RepoAt string 30 + CommentId int 31 + CommentAt string 32 + Body string 33 + Created time.Time 31 34 } 32 35 33 - func NewPull(tx *sql.Tx, pull *Pulls) error { 36 + func NewPull(tx *sql.Tx, pull *Pull) error { 34 37 defer tx.Rollback() 35 38 36 39 _, err := tx.Exec(` ··· 55 58 pull.PullId = nextId 56 59 57 60 _, err = tx.Exec(` 58 - insert into pulls (repo_at, owner_did, pull_id, title, patch) 59 - values (?, ?, ?, ?, ?) 60 - `, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.Patch) 61 + insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, patch, rkey) 62 + values (?, ?, ?, ?, ?, ?, ?, ?) 63 + `, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.TargetBranch, pull.Body, pull.Patch, pull.Rkey) 61 64 if err != nil { 62 65 return err 63 66 } ··· 70 73 } 71 74 72 75 func SetPullAt(e Execer, repoAt syntax.ATURI, pullId int, pullAt string) error { 73 - _, err := e.Exec(`update pulls set patch_at = ? where repo_at = ? and pull_id = ?`, pullAt, repoAt, pullId) 76 + _, err := e.Exec(`update pulls set pull_at = ? where repo_at = ? and pull_id = ?`, pullAt, repoAt, pullId) 74 77 return err 75 78 } 76 79 77 80 func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (string, error) { 78 81 var pullAt string 79 - err := e.QueryRow(`select patch_at from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&pullAt) 82 + err := e.QueryRow(`select pull_at from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&pullAt) 80 83 return pullAt, err 81 84 } 82 85 ··· 92 95 return ownerDid, err 93 96 } 94 97 95 - func GetPulls(e Execer, repoAt syntax.ATURI) ([]Pulls, error) { 96 - var pulls []Pulls 98 + func GetPulls(e Execer, repoAt syntax.ATURI) ([]Pull, error) { 99 + var pulls []Pull 97 100 98 - rows, err := e.Query(`select owner_did, pull_id, created, title, patch, open from pulls where repo_at = ? order by created desc`, repoAt) 101 + rows, err := e.Query(`select owner_did, pull_id, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? order by created desc`, repoAt) 99 102 if err != nil { 100 103 return nil, err 101 104 } 102 105 defer rows.Close() 103 106 104 107 for rows.Next() { 105 - var pull Pulls 108 + var pull Pull 106 109 var createdAt string 107 - err := rows.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Patch, &pull.Open) 110 + err := rows.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey) 108 111 if err != nil { 109 112 return nil, err 110 113 } ··· 125 128 return pulls, nil 126 129 } 127 130 128 - func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pulls, error) { 129 - query := `select owner_did, created, title, patch, open from pulls where repo_at = ? and pull_id = ?` 131 + func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) { 132 + query := `select owner_did, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?` 130 133 row := e.QueryRow(query, repoAt, pullId) 131 134 132 - var pull Pulls 135 + var pull Pull 133 136 var createdAt string 134 - err := row.Scan(&pull.OwnerDid, &createdAt, &pull.Title, &pull.Patch, &pull.Open) 137 + err := row.Scan(&pull.OwnerDid, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey) 135 138 if err != nil { 136 139 return nil, err 137 140 } ··· 145 148 return &pull, nil 146 149 } 147 150 148 - func GetPullWithComments(e Execer, repoAt syntax.ATURI, pullId int) (*Pulls, []PullComments, error) { 149 - query := `select owner_did, pull_id, created, title, patch, open from pulls where repo_at = ? and pull_id = ?` 151 + func GetPullWithComments(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, []PullComment, error) { 152 + query := `select owner_did, pull_id, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?` 150 153 row := e.QueryRow(query, repoAt, pullId) 151 154 152 - var pull Pulls 155 + var pull Pull 153 156 var createdAt string 154 - err := row.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Patch, &pull.Open) 157 + err := row.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey) 155 158 if err != nil { 156 159 return nil, nil, err 157 160 } ··· 170 173 return &pull, comments, nil 171 174 } 172 175 173 - func NewPullComment(e Execer, comment *PullComments) error { 176 + func NewPullComment(e Execer, comment *PullComment) error { 174 177 query := `insert into pull_comments (owner_did, repo_at, comment_at, pull_id, comment_id, body) values (?, ?, ?, ?, ?, ?)` 175 178 _, err := e.Exec( 176 179 query, ··· 184 187 return err 185 188 } 186 189 187 - func GetPullComments(e Execer, repoAt syntax.ATURI, pullId int) ([]PullComments, error) { 188 - var comments []PullComments 190 + func GetPullComments(e Execer, repoAt syntax.ATURI, pullId int) ([]PullComment, error) { 191 + var comments []PullComment 189 192 190 193 rows, err := e.Query(`select owner_did, pull_id, comment_id, comment_at, body, created from pull_comments where repo_at = ? and pull_id = ? order by created asc`, repoAt, pullId) 191 194 if err == sql.ErrNoRows { 192 - return []PullComments{}, nil 195 + return []PullComment{}, nil 193 196 } 194 197 if err != nil { 195 198 return nil, err ··· 197 200 defer rows.Close() 198 201 199 202 for rows.Next() { 200 - var comment PullComments 203 + var comment PullComment 201 204 var createdAt string 202 205 err := rows.Scan(&comment.OwnerDid, &comment.PullId, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt) 203 206 if err != nil {
+27
appview/pages/pages.go
··· 506 506 return p.executeRepo("repo/issues/new", w, params) 507 507 } 508 508 509 + type RepoNewPullParams struct { 510 + LoggedInUser *auth.User 511 + RepoInfo RepoInfo 512 + Active string 513 + } 514 + 515 + func (p *Pages) RepoNewPull(w io.Writer, params RepoNewPullParams) error { 516 + params.Active = "pulls" 517 + return p.executeRepo("repo/pulls/new", w, params) 518 + } 519 + 509 520 type RepoPullsParams struct { 510 521 LoggedInUser *auth.User 511 522 RepoInfo RepoInfo 523 + Pulls []db.Pull 512 524 Active string 525 + DidHandleMap map[string]string 513 526 } 514 527 515 528 func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { 516 529 params.Active = "pulls" 517 530 return p.executeRepo("repo/pulls/pulls", w, params) 531 + } 532 + 533 + type RepoSinglePullParams struct { 534 + LoggedInUser *auth.User 535 + RepoInfo RepoInfo 536 + DidHandleMap map[string]string 537 + Pull db.Pull 538 + Comments []db.PullComment 539 + Active string 540 + } 541 + 542 + func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { 543 + params.Active = "pulls" 544 + return p.executeRepo("repo/pulls/pull", w, params) 518 545 } 519 546 520 547 func (p *Pages) Static() http.Handler {
+38
appview/pages/templates/repo/pulls/new.html
··· 1 + {{ define "title" }}new pull | {{ .RepoInfo.FullName }}{{ end }} 2 + 3 + {{ define "repoContent" }} 4 + <form 5 + hx-post="/{{ .RepoInfo.FullName }}/pulls/new" 6 + class="mt-6 space-y-6" 7 + hx-swap="none" 8 + > 9 + <div class="flex flex-col gap-4"> 10 + <div> 11 + <label for="title">title</label> 12 + <input type="text" name="title" id="title" class="w-full" /> 13 + <input type="text" name="targetBranch" id="targetBranch" /> 14 + </div> 15 + <div> 16 + <label for="body">body</label> 17 + <textarea 18 + name="body" 19 + id="body" 20 + rows="6" 21 + class="w-full resize-y" 22 + placeholder="Describe your change. Markdown is supported." 23 + ></textarea> 24 + <textarea 25 + name="patch" 26 + id="patch" 27 + rows="10" 28 + class="w-full resize-y font-mono" 29 + placeholder="Paste your git-format-patch output here." 30 + ></textarea> 31 + </div> 32 + <div> 33 + <button type="submit" class="btn">create</button> 34 + </div> 35 + </div> 36 + <div id="pull" class="error"></div> 37 + </form> 38 + {{ end }}
+133
appview/pages/templates/repo/pulls/pull.html
··· 1 + {{ define "title" }} 2 + {{ .Pull.Title }} &middot; 3 + {{ .RepoInfo.FullName }} 4 + {{ end }} 5 + 6 + {{ define "repoContent" }} 7 + <h1> 8 + {{ .Pull.Title }} 9 + <span class="text-gray-400">#{{ .Pull.PullId }}</span> 10 + </h1> 11 + 12 + {{ $bgColor := "bg-gray-800" }} 13 + {{ $icon := "ban" }} 14 + {{ if eq .State "open" }} 15 + {{ $bgColor = "bg-green-600" }} 16 + {{ $icon = "circle-dot" }} 17 + {{ end }} 18 + 19 + 20 + <section> 21 + <div class="flex items-center gap-2"> 22 + <div 23 + id="state" 24 + class="inline-flex items-center rounded px-3 py-1 {{ $bgColor }}" 25 + > 26 + <i 27 + data-lucide="{{ $icon }}" 28 + class="w-4 h-4 mr-1.5 text-white" 29 + ></i> 30 + <span class="text-white">{{ .State }}</span> 31 + </div> 32 + <span class="text-gray-400 text-sm"> 33 + opened by 34 + {{ $owner := didOrHandle .Pull.OwnerDid .PullOwnerHandle }} 35 + <a href="/{{ $owner }}" class="no-underline hover:underline" 36 + >{{ $owner }}</a 37 + > 38 + <span class="px-1 select-none before:content-['\00B7']"></span> 39 + <time>{{ .Pull.Created | timeFmt }}</time> 40 + </span> 41 + </div> 42 + 43 + {{ if .Pull.Body }} 44 + <article id="body" class="mt-8 prose"> 45 + {{ .Pull.Body | markdown }} 46 + </article> 47 + {{ end }} 48 + </section> 49 + {{ end }} 50 + 51 + {{ define "repoAfter" }} 52 + <section id="comments" class="mt-8 space-y-4 relative"> 53 + {{ range $index, $comment := .Comments }} 54 + <div 55 + id="comment-{{ .CommentId }}" 56 + class="rounded bg-white p-4 relative" 57 + > 58 + {{ if eq $index 0 }} 59 + <div 60 + class="absolute left-8 -top-8 w-px h-8 bg-gray-300" 61 + ></div> 62 + {{ else }} 63 + <div 64 + class="absolute left-8 -top-4 w-px h-4 bg-gray-300" 65 + ></div> 66 + {{ end }} 67 + <div class="flex items-center gap-2 mb-2 text-gray-400"> 68 + {{ $owner := index $.DidHandleMap .OwnerDid }} 69 + <span class="text-sm"> 70 + <a 71 + href="/{{ $owner }}" 72 + class="no-underline hover:underline" 73 + >{{ $owner }}</a 74 + > 75 + </span> 76 + <span 77 + class="px-1 select-none before:content-['\00B7']" 78 + ></span> 79 + <a 80 + href="#{{ .CommentId }}" 81 + class="text-gray-500 text-sm hover:text-gray-500 hover:underline no-underline" 82 + id="{{ .CommentId }}" 83 + > 84 + {{ .Created | timeFmt }} 85 + </a> 86 + </div> 87 + <div class="prose"> 88 + {{ .Body | markdown }} 89 + </div> 90 + </div> 91 + {{ end }} 92 + </section> 93 + 94 + {{ if .LoggedInUser }} 95 + <form 96 + hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/comment" 97 + class="mt-8" 98 + > 99 + <textarea 100 + name="body" 101 + class="w-full p-2 rounded border border-gray-200" 102 + placeholder="Add to the discussion..." 103 + ></textarea> 104 + <button type="submit" class="btn mt-2">comment</button> 105 + <div id="pull-comment"></div> 106 + </form> 107 + {{ end }} 108 + 109 + {{ if eq .LoggedInUser.Did .Pull.OwnerDid }} 110 + {{ $action := "close" }} 111 + {{ $icon := "circle-x" }} 112 + {{ $hoverColor := "red" }} 113 + {{ if eq .State "closed" }} 114 + {{ $action = "reopen" }} 115 + {{ $icon = "circle-dot" }} 116 + {{ $hoverColor = "green" }} 117 + {{ end }} 118 + <form 119 + hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}" 120 + hx-swap="none" 121 + class="mt-8" 122 + > 123 + <button type="submit" class="btn hover:bg-{{ $hoverColor }}-300"> 124 + <i 125 + data-lucide="{{ $icon }}" 126 + class="w-4 h-4 mr-2 text-{{ $hoverColor }}-400" 127 + ></i> 128 + <span class="text-black">{{ $action }}</span> 129 + </button> 130 + <div id="pull-action" class="error"></div> 131 + </form> 132 + {{ end }} 133 + {{ end }}
+221 -6
appview/state/repo.go
··· 234 234 } 235 235 } 236 236 237 + // MergeCheck gets called async, every time the patch diff is updated in a pull. 238 + func (s *State) MergeCheck(w http.ResponseWriter, r *http.Request) { 239 + user := s.auth.GetUser(r) 240 + f, err := fullyResolvedRepo(r) 241 + if err != nil { 242 + log.Println("failed to get repo and knot", err) 243 + s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.") 244 + return 245 + } 246 + 247 + patch := r.FormValue("patch") 248 + targetBranch := r.FormValue("targetBranch") 249 + 250 + if patch == "" || targetBranch == "" { 251 + s.pages.Notice(w, "pull", "Patch and target branch are required.") 252 + return 253 + } 254 + 255 + secret, err := db.GetRegistrationKey(s.db, f.Knot) 256 + if err != nil { 257 + log.Printf("no key found for domain %s: %s\n", f.Knot, err) 258 + s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.") 259 + return 260 + } 261 + 262 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 263 + if err != nil { 264 + log.Printf("failed to create signed client for %s", f.Knot) 265 + s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.") 266 + return 267 + } 268 + 269 + resp, err := ksClient.MergeCheck([]byte(patch), user.Did, f.RepoName, targetBranch) 270 + if err != nil { 271 + log.Println("failed to check mergeability", err) 272 + s.pages.Notice(w, "pull", "Unable to check for mergeability. Try again later.") 273 + return 274 + } 275 + 276 + respBody, err := io.ReadAll(resp.Body) 277 + if err != nil { 278 + log.Println("failed to read knotserver response body") 279 + s.pages.Notice(w, "pull", "Unable to check for mergeability. Try again later.") 280 + return 281 + } 282 + 283 + var mergeCheckResponse types.MergeCheckResponse 284 + err = json.Unmarshal(respBody, &mergeCheckResponse) 285 + if err != nil { 286 + log.Println("failed to unmarshal merge check response", err) 287 + s.pages.Notice(w, "pull", "Failed to check mergeability. Try again later.") 288 + return 289 + } 290 + 291 + // TODO: this has to return a html fragment 292 + w.Header().Set("Content-Type", "application/json") 293 + json.NewEncoder(w).Encode(mergeCheckResponse) 294 + } 295 + 296 + func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 297 + user := s.auth.GetUser(r) 298 + f, err := fullyResolvedRepo(r) 299 + if err != nil { 300 + log.Println("failed to get repo and knot", err) 301 + return 302 + } 303 + 304 + switch r.Method { 305 + case http.MethodGet: 306 + s.pages.RepoNewPull(w, pages.RepoNewPullParams{ 307 + LoggedInUser: user, 308 + RepoInfo: f.RepoInfo(s, user), 309 + }) 310 + case http.MethodPost: 311 + title := r.FormValue("title") 312 + body := r.FormValue("body") 313 + targetBranch := r.FormValue("targetBranch") 314 + patch := r.FormValue("patch") 315 + 316 + if title == "" || body == "" || patch == "" { 317 + s.pages.Notice(w, "pull", "Title, body and patch diff are required.") 318 + return 319 + } 320 + 321 + tx, err := s.db.BeginTx(r.Context(), nil) 322 + if err != nil { 323 + log.Println("failed to start tx") 324 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 325 + return 326 + } 327 + 328 + defer func() { 329 + tx.Rollback() 330 + err = s.enforcer.E.LoadPolicy() 331 + if err != nil { 332 + log.Println("failed to rollback policies") 333 + } 334 + }() 335 + 336 + err = db.NewPull(tx, &db.Pull{ 337 + Title: title, 338 + Body: body, 339 + TargetBranch: targetBranch, 340 + Patch: patch, 341 + OwnerDid: user.Did, 342 + RepoAt: f.RepoAt, 343 + }) 344 + if err != nil { 345 + log.Println("failed to create pull request", err) 346 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 347 + return 348 + } 349 + client, _ := s.auth.AuthorizedClient(r) 350 + pullId, err := db.GetPullId(s.db, f.RepoAt) 351 + if err != nil { 352 + log.Println("failed to get pull id", err) 353 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 354 + return 355 + } 356 + 357 + atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 358 + Collection: tangled.RepoPullNSID, 359 + Repo: user.Did, 360 + Rkey: s.TID(), 361 + Record: &lexutil.LexiconTypeDecoder{ 362 + Val: &tangled.RepoPull{ 363 + Title: title, 364 + PullId: int64(pullId), 365 + TargetRepo: string(f.RepoAt), 366 + TargetBranch: targetBranch, 367 + Patch: patch, 368 + }, 369 + }, 370 + }) 371 + 372 + err = db.SetPullAt(s.db, f.RepoAt, pullId, atResp.Uri) 373 + if err != nil { 374 + log.Println("failed to get pull id", err) 375 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 376 + return 377 + } 378 + 379 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId)) 380 + return 381 + } 382 + } 383 + 384 + func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) { 385 + user := s.auth.GetUser(r) 386 + f, err := fullyResolvedRepo(r) 387 + if err != nil { 388 + log.Println("failed to get repo and knot", err) 389 + return 390 + } 391 + 392 + prId := chi.URLParam(r, "pull") 393 + prIdInt, err := strconv.Atoi(prId) 394 + if err != nil { 395 + http.Error(w, "bad pr id", http.StatusBadRequest) 396 + log.Println("failed to parse pr id", err) 397 + return 398 + } 399 + 400 + pr, comments, err := db.GetPullWithComments(s.db, f.RepoAt, prIdInt) 401 + if err != nil { 402 + log.Println("failed to get pr and comments", err) 403 + s.pages.Notice(w, "pull", "Failed to load pull request. Try again later.") 404 + return 405 + } 406 + 407 + identsToResolve := make([]string, len(comments)) 408 + for i, comment := range comments { 409 + identsToResolve[i] = comment.OwnerDid 410 + } 411 + resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 412 + didHandleMap := make(map[string]string) 413 + for _, identity := range resolvedIds { 414 + if !identity.Handle.IsInvalidHandle() { 415 + didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 416 + } else { 417 + didHandleMap[identity.DID.String()] = identity.DID.String() 418 + } 419 + } 420 + 421 + s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 422 + LoggedInUser: user, 423 + RepoInfo: f.RepoInfo(s, user), 424 + Pull: *pr, 425 + Comments: comments, 426 + 427 + DidHandleMap: didHandleMap, 428 + }) 429 + } 430 + 237 431 func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) { 238 432 f, err := fullyResolvedRepo(r) 239 433 if err != nil { ··· 1055 1249 return 1056 1250 } 1057 1251 1058 - switch r.Method { 1059 - case http.MethodGet: 1060 - s.pages.RepoPulls(w, pages.RepoPullsParams{ 1061 - LoggedInUser: user, 1062 - RepoInfo: f.RepoInfo(s, user), 1063 - }) 1252 + pulls, err := db.GetPulls(s.db, f.RepoAt) 1253 + if err != nil { 1254 + log.Println("failed to get pulls", err) 1255 + s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.") 1256 + return 1257 + } 1258 + 1259 + identsToResolve := make([]string, len(pulls)) 1260 + for i, pull := range pulls { 1261 + identsToResolve[i] = pull.OwnerDid 1262 + } 1263 + resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 1264 + didHandleMap := make(map[string]string) 1265 + for _, identity := range resolvedIds { 1266 + if !identity.Handle.IsInvalidHandle() { 1267 + didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String()) 1268 + } else { 1269 + didHandleMap[identity.DID.String()] = identity.DID.String() 1270 + } 1064 1271 } 1272 + 1273 + s.pages.RepoPulls(w, pages.RepoPullsParams{ 1274 + LoggedInUser: s.auth.GetUser(r), 1275 + RepoInfo: f.RepoInfo(s, user), 1276 + Pulls: pulls, 1277 + DidHandleMap: didHandleMap, 1278 + }) 1279 + return 1065 1280 } 1066 1281 1067 1282 func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
+19
appview/state/signer.go
··· 174 174 175 175 return s.client.Do(req) 176 176 } 177 + 178 + func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) { 179 + const ( 180 + Method = "POST" 181 + ) 182 + endpoint := fmt.Sprintf("/%s/%s/merge/check", ownerDid, targetRepo) 183 + 184 + body, _ := json.Marshal(map[string]interface{}{ 185 + "patch": string(patch), 186 + "branch": branch, 187 + }) 188 + 189 + req, err := s.newRequest(Method, endpoint, body) 190 + if err != nil { 191 + return nil, err 192 + } 193 + 194 + return s.client.Do(req) 195 + }
+11
appview/state/state.go
··· 881 881 882 882 r.Route("/pulls", func(r chi.Router) { 883 883 r.Get("/", s.RepoPulls) 884 + r.Get("/{pull}", s.RepoSinglePull) 885 + 886 + r.Group(func(r chi.Router) { 887 + r.Use(AuthMiddleware(s)) 888 + r.Get("/new", s.NewPull) 889 + r.Post("/new", s.NewPull) 890 + // r.Post("/{pull}/comment", s.PullComment) 891 + // r.Post("/{pull}/close", s.ClosePull) 892 + // r.Post("/{pull}/reopen", s.ReopenPull) 893 + // r.Post("/{pull}/merge", s.MergePull) 894 + }) 884 895 }) 885 896 886 897 // These routes get proxied to the knot
+2 -2
cmd/gen.go
··· 22 22 shtangled.RepoIssueState{}, 23 23 shtangled.RepoIssue{}, 24 24 shtangled.Repo{}, 25 - shtangled.RepoPullPatch{}, 26 - shtangled.RepoPullState{}, 25 + shtangled.RepoPull{}, 26 + shtangled.RepoPullStatus{}, 27 27 shtangled.RepoPullComment{}, 28 28 ); err != nil { 29 29 panic(err)