Monorepo for Tangled
tangled.org
1package models
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8 "tangled.org/core/api/tangled"
9)
10
11type Pull2 struct {
12 // TODO: PullId is not atprotated yet
13 PullId int
14 Did syntax.DID
15 Rkey syntax.RecordKey
16
17 Submissions []*PullRound // PR submission history
18
19 // backlinked records
20
21 State PullState
22 Labels LabelState
23 Comments []Comment
24
25 // optionally, populate these when querying for reverse mappings
26
27 Repo *Repo
28}
29
30// PullRound represents snapshot of `sh.tangled.repo.pull` record
31// NOTE: Non-patch change can make new submission
32type PullRound struct {
33 Id int64
34 Round int
35 Did syntax.DID
36 Rkey syntax.RecordKey
37 Cid syntax.CID
38
39 // content
40
41 Target PullTarget
42 Source *PullSource2 // optional for patch based PR
43 Patch string // list of commits encoded in patch object
44 Title string
45 Body string
46 Mentions []syntax.DID
47 References []syntax.ATURI
48 Created time.Time
49}
50
51type PullTarget struct {
52 RepoAt syntax.ATURI
53 Branch string
54}
55
56// PullSource2 is not a source of truth but rather a metadata to help resubmitting
57type PullSource2 struct {
58 RepoAt syntax.ATURI
59 Branch string
60}
61
62func (p Pull2) AsRecord() tangled.RepoPull {
63 return p.Latest().AsRecord()
64}
65
66func (p *Pull2) AtUri() syntax.ATURI {
67 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.RepoPullNSID, p.Rkey))
68}
69
70func (p *Pull2) Latest() *PullRound {
71 return p.Submissions[len(p.Submissions)-1]
72}
73
74func (p *Pull2) CID() syntax.CID {
75 return p.Latest().Cid
76}
77
78func (p *Pull2) Created() time.Time {
79 return p.Submissions[0].Created
80}
81
82func (p *Pull2) Edited() time.Time {
83 return p.Latest().Created
84}
85
86func (p *Pull2) RepoAt() syntax.ATURI {
87 return p.Latest().Target.RepoAt
88}
89
90func (p *Pull2) Validate() error {
91 if len(p.Submissions) == 0 {
92 return fmt.Errorf("Pull should include at least one submission")
93 }
94 // we don't need to validate existing records
95 if err := p.Latest().Validate(); err != nil {
96 return fmt.Errorf("validate latest stack: %w", err)
97 }
98 return nil
99}
100
101func PullContentFromRecord(cid syntax.CID, record tangled.RepoPull) PullRound {
102 var source *PullSource2
103 if record.Source != nil {
104 source = &PullSource2{}
105 source.Branch = record.Source.Branch
106 if record.Source.Repo != nil {
107 source.RepoAt = syntax.ATURI(*record.Source.Repo)
108 } else {
109 source.RepoAt = syntax.ATURI(record.Target.Repo)
110 }
111 }
112 var (
113 body string
114 mentions = make([]syntax.DID, len(record.Mentions))
115 references = make([]syntax.ATURI, len(record.References))
116 )
117 if record.Body != nil {
118 body = *record.Body
119 }
120 for i, v := range record.Mentions {
121 mentions[i] = syntax.DID(v)
122 }
123 for i, v := range record.References {
124 references[i] = syntax.ATURI(v)
125 }
126 created, err := time.Parse(record.CreatedAt, time.RFC3339)
127 if err != nil {
128 created = time.Now()
129 }
130 return PullRound{
131 Cid: cid,
132 Target: PullTarget{
133 RepoAt: syntax.ATURI(record.Target.Repo),
134 Branch: record.Target.Branch,
135 },
136 Source: source,
137 Patch: record.Patch,
138 Title: record.Title,
139 Body: body,
140 Created: created,
141 }
142}
143
144// NOTE: AsRecord doesn't include the patch blob id in returned atproto record
145func (p PullRound) AsRecord() tangled.RepoPull {
146 var source *tangled.RepoPull_Source
147
148 if p.Source != nil {
149 repoAt := p.Source.RepoAt.String()
150 source = &tangled.RepoPull_Source{
151 Repo: &repoAt,
152 Branch: p.Source.Branch,
153 }
154 }
155
156 var body *string
157 if p.Body != "" {
158 body = &p.Body
159 }
160 return tangled.RepoPull{
161 Target: &tangled.RepoPull_Target{
162 Repo: p.Target.RepoAt.String(),
163 Branch: p.Target.Branch,
164 },
165 Source: source,
166 Title: p.Title,
167 Body: body,
168 Mentions: toStringList(p.Mentions),
169 References: toStringList(p.References),
170 }
171}
172
173func (p *PullRound) AtUri() syntax.ATURI {
174 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.RepoPullNSID, p.Rkey))
175}
176
177func (p *PullRound) Validate() error {
178 // TODO: validate patches
179 return nil
180}
181
182func toStringList[T fmt.Stringer](list []T) []string {
183 slist := make([]string, len(list))
184 for i, v := range list {
185 slist[i] = v.String()
186 }
187 return slist
188}