+89
appview/pulls/pulls.go
+89
appview/pulls/pulls.go
···
691
691
})
692
692
}
693
693
694
+
// ListPulls is an XRPC method that lists all pull requests targeting a repository
695
+
func (s *Pulls) ListPulls(w http.ResponseWriter, r *http.Request) {
696
+
l := s.logger.With("handler", "ListPulls")
697
+
698
+
// Parse query parameters
699
+
params := r.URL.Query()
700
+
repoAtStr := params.Get("repo") // AT-URI of target repo
701
+
stateParam := params.Get("state") // "open", "closed", "merged", or empty for all
702
+
703
+
if repoAtStr == "" {
704
+
http.Error(w, "repo parameter is required", http.StatusBadRequest)
705
+
return
706
+
}
707
+
708
+
// Parse AT-URI and validate
709
+
repoAt, err := syntax.ParseATURI(repoAtStr)
710
+
if err != nil {
711
+
l.Error("invalid repo AT-URI", "err", err, "repoAt", repoAtStr)
712
+
http.Error(w, "invalid repo AT-URI", http.StatusBadRequest)
713
+
return
714
+
}
715
+
716
+
// Determine state filter
717
+
var stateFilter orm.Filter
718
+
switch stateParam {
719
+
case "open":
720
+
stateFilter = orm.FilterEq("state", models.PullOpen)
721
+
case "closed":
722
+
stateFilter = orm.FilterEq("state", models.PullClosed)
723
+
case "merged":
724
+
stateFilter = orm.FilterEq("state", models.PullMerged)
725
+
case "":
726
+
// No state filter - include all states
727
+
stateFilter = orm.FilterIn("state", []models.PullState{
728
+
models.PullOpen,
729
+
models.PullClosed,
730
+
models.PullMerged,
731
+
})
732
+
default:
733
+
http.Error(w, "invalid state parameter", http.StatusBadRequest)
734
+
return
735
+
}
736
+
737
+
// Query database
738
+
pulls, err := db.GetPulls(
739
+
s.db,
740
+
orm.FilterEq("repo_at", repoAt.String()),
741
+
stateFilter,
742
+
)
743
+
if err != nil {
744
+
l.Error("failed to list pulls", "err", err)
745
+
http.Error(w, "failed to list pulls", http.StatusInternalServerError)
746
+
return
747
+
}
748
+
749
+
l.Debug("listed pulls", "count", len(pulls), "repo_at", repoAt.String())
750
+
751
+
// Build response
752
+
type PullSummary struct {
753
+
Rkey string `json:"rkey"`
754
+
OwnerDid string `json:"ownerDid"`
755
+
PullId int `json:"pullId"`
756
+
Title string `json:"title"`
757
+
State int `json:"state"`
758
+
TargetBranch string `json:"targetBranch"`
759
+
CreatedAt string `json:"createdAt"`
760
+
}
761
+
762
+
type Response struct {
763
+
Pulls []PullSummary `json:"pulls"`
764
+
}
765
+
766
+
summaries := make([]PullSummary, 0, len(pulls))
767
+
for _, p := range pulls {
768
+
summaries = append(summaries, PullSummary{
769
+
Rkey: p.Rkey,
770
+
OwnerDid: p.OwnerDid,
771
+
PullId: p.PullId,
772
+
Title: p.Title,
773
+
State: int(p.State),
774
+
TargetBranch: p.TargetBranch,
775
+
CreatedAt: p.Created.Format(time.RFC3339),
776
+
})
777
+
}
778
+
779
+
w.Header().Set("Content-Type", "application/json")
780
+
json.NewEncoder(w).Encode(Response{Pulls: summaries})
781
+
}
782
+
694
783
func (s *Pulls) PullComment(w http.ResponseWriter, r *http.Request) {
695
784
user := s.oauth.GetUser(r)
696
785
f, err := s.repoResolver.Resolve(r)
+3
appview/state/router.go
+3
appview/state/router.go
···
143
143
144
144
r.With(middleware.Paginate).Get("/goodfirstissues", s.GoodFirstIssues)
145
145
146
+
// XRPC endpoints
147
+
r.Get("/xrpc/sh.tangled.repo.listPulls", s.ListPulls)
148
+
146
149
r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) {
147
150
r.Post("/", s.Follow)
148
151
r.Delete("/", s.Follow)
+26
appview/state/xrpc.go
+26
appview/state/xrpc.go
···
1
+
package state
2
+
3
+
import (
4
+
"net/http"
5
+
6
+
"tangled.org/core/appview/pulls"
7
+
)
8
+
9
+
// ListPulls delegates to the pulls.ListPulls XRPC handler
10
+
func (s *State) ListPulls(w http.ResponseWriter, r *http.Request) {
11
+
pullsHandler := pulls.New(
12
+
s.oauth,
13
+
s.repoResolver,
14
+
s.pages,
15
+
s.idResolver,
16
+
s.mentionsResolver,
17
+
s.db,
18
+
s.config,
19
+
s.notifier,
20
+
s.enforcer,
21
+
s.validator,
22
+
s.indexer.Pulls,
23
+
s.logger.With("handler", "pulls-xrpc"),
24
+
)
25
+
pullsHandler.ListPulls(w, r)
26
+
}
+80
lexicons/repo/listPulls.json
+80
lexicons/repo/listPulls.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.tangled.repo.listPulls",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"parameters": {
8
+
"type": "params",
9
+
"required": [
10
+
"repo"
11
+
],
12
+
"properties": {
13
+
"repo": {
14
+
"type": "string",
15
+
"format": "at-uri"
16
+
},
17
+
"state": {
18
+
"type": "string",
19
+
"enum": ["open", "closed", "merged"]
20
+
}
21
+
}
22
+
},
23
+
"output": {
24
+
"encoding": "application/json",
25
+
"schema": {
26
+
"type": "object",
27
+
"required": [
28
+
"pulls"
29
+
],
30
+
"properties": {
31
+
"pulls": {
32
+
"type": "array",
33
+
"items": {
34
+
"type": "ref",
35
+
"ref": "#pull"
36
+
}
37
+
}
38
+
}
39
+
}
40
+
}
41
+
},
42
+
"pull": {
43
+
"type": "object",
44
+
"required": [
45
+
"rkey",
46
+
"ownerDid",
47
+
"pullId",
48
+
"title",
49
+
"state",
50
+
"targetBranch",
51
+
"createdAt"
52
+
],
53
+
"properties": {
54
+
"rkey": {
55
+
"type": "string"
56
+
},
57
+
"ownerDid": {
58
+
"type": "string",
59
+
"format": "did"
60
+
},
61
+
"pullId": {
62
+
"type": "integer"
63
+
},
64
+
"title": {
65
+
"type": "string"
66
+
},
67
+
"state": {
68
+
"type": "integer"
69
+
},
70
+
"targetBranch": {
71
+
"type": "string"
72
+
},
73
+
"createdAt": {
74
+
"type": "string",
75
+
"format": "datetime"
76
+
}
77
+
}
78
+
}
79
+
}
80
+
}