Monorepo for Tangled
tangled.org
1package models
2
3import (
4 "fmt"
5 "sort"
6 "time"
7
8 "github.com/bluesky-social/indigo/atproto/syntax"
9 "tangled.org/core/api/tangled"
10)
11
12type Issue struct {
13 Id int64
14 Did string
15 Rkey string
16 RepoDid syntax.DID
17 IssueId int
18 Created time.Time
19 Edited *time.Time
20 Deleted *time.Time
21 Title string
22 Body string
23 Open bool
24 Mentions []syntax.DID
25 References []syntax.ATURI
26
27 // optionally, populate this when querying for reverse mappings
28 // like comment counts, parent repo etc.
29 Comments []IssueComment
30 Labels LabelState
31 Repo *Repo
32}
33
34func (i *Issue) AtUri() syntax.ATURI {
35 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueNSID, i.Rkey))
36}
37
38func (i *Issue) AsRecord() tangled.RepoIssue {
39 mentions := make([]string, len(i.Mentions))
40 for i, did := range i.Mentions {
41 mentions[i] = string(did)
42 }
43 references := make([]string, len(i.References))
44 for i, uri := range i.References {
45 references[i] = string(uri)
46 }
47 rec := tangled.RepoIssue{
48 Repo: string(i.RepoDid),
49 Title: i.Title,
50 Body: &i.Body,
51 Mentions: mentions,
52 References: references,
53 CreatedAt: i.Created.Format(time.RFC3339),
54 }
55 return rec
56}
57
58func (i *Issue) State() string {
59 if i.Open {
60 return "open"
61 }
62 return "closed"
63}
64
65type CommentListItem struct {
66 Self *IssueComment
67 Replies []*IssueComment
68}
69
70func (it *CommentListItem) Participants() []syntax.DID {
71 participantSet := make(map[syntax.DID]struct{})
72 participants := []syntax.DID{}
73
74 addParticipant := func(did syntax.DID) {
75 if _, exists := participantSet[did]; !exists {
76 participantSet[did] = struct{}{}
77 participants = append(participants, did)
78 }
79 }
80
81 addParticipant(syntax.DID(it.Self.Did))
82
83 for _, c := range it.Replies {
84 addParticipant(syntax.DID(c.Did))
85 }
86
87 return participants
88}
89
90func (i *Issue) CommentList() []CommentListItem {
91 // Create a map to quickly find comments by their aturi
92 toplevel := make(map[string]*CommentListItem)
93 var replies []*IssueComment
94
95 // collect top level comments into the map
96 for _, comment := range i.Comments {
97 if comment.IsTopLevel() {
98 toplevel[comment.AtUri().String()] = &CommentListItem{
99 Self: &comment,
100 }
101 } else {
102 replies = append(replies, &comment)
103 }
104 }
105
106 for _, r := range replies {
107 parentAt := *r.ReplyTo
108 if parent, exists := toplevel[parentAt]; exists {
109 parent.Replies = append(parent.Replies, r)
110 }
111 }
112
113 var listing []CommentListItem
114 for _, v := range toplevel {
115 listing = append(listing, *v)
116 }
117
118 // sort everything
119 sortFunc := func(a, b *IssueComment) bool {
120 return a.Created.Before(b.Created)
121 }
122 sort.Slice(listing, func(i, j int) bool {
123 return sortFunc(listing[i].Self, listing[j].Self)
124 })
125 for _, r := range listing {
126 sort.Slice(r.Replies, func(i, j int) bool {
127 return sortFunc(r.Replies[i], r.Replies[j])
128 })
129 }
130
131 return listing
132}
133
134func (i *Issue) Participants() []syntax.DID {
135 participantSet := make(map[syntax.DID]struct{})
136 participants := []syntax.DID{}
137
138 addParticipant := func(did syntax.DID) {
139 if _, exists := participantSet[did]; !exists {
140 participantSet[did] = struct{}{}
141 participants = append(participants, did)
142 }
143 }
144
145 addParticipant(syntax.DID(i.Did))
146
147 for _, c := range i.Comments {
148 addParticipant(syntax.DID(c.Did))
149 }
150
151 return participants
152}
153
154func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue {
155 created, err := time.Parse(time.RFC3339, record.CreatedAt)
156 if err != nil {
157 created = time.Now()
158 }
159
160 body := ""
161 if record.Body != nil {
162 body = *record.Body
163 }
164
165 return Issue{
166 RepoDid: syntax.DID(record.Repo),
167 Did: did,
168 Rkey: rkey,
169 Created: created,
170 Title: record.Title,
171 Body: body,
172 Open: true, // new issues are open by default
173 }
174}
175
176type IssueComment struct {
177 Id int64
178 Did string
179 Rkey string
180 IssueAt string
181 ReplyTo *string
182 Body string
183 Created time.Time
184 Edited *time.Time
185 Deleted *time.Time
186 Mentions []syntax.DID
187 References []syntax.ATURI
188}
189
190func (i *IssueComment) AtUri() syntax.ATURI {
191 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
192}
193
194func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
195 mentions := make([]string, len(i.Mentions))
196 for i, did := range i.Mentions {
197 mentions[i] = string(did)
198 }
199 references := make([]string, len(i.References))
200 for i, uri := range i.References {
201 references[i] = string(uri)
202 }
203 return tangled.RepoIssueComment{
204 Body: i.Body,
205 Issue: i.IssueAt,
206 CreatedAt: i.Created.Format(time.RFC3339),
207 ReplyTo: i.ReplyTo,
208 Mentions: mentions,
209 References: references,
210 }
211}
212
213func (i *IssueComment) IsTopLevel() bool {
214 return i.ReplyTo == nil
215}
216
217func (i *IssueComment) IsReply() bool {
218 return i.ReplyTo != nil
219}
220
221func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
222 created, err := time.Parse(time.RFC3339, record.CreatedAt)
223 if err != nil {
224 created = time.Now()
225 }
226
227 ownerDid := did
228
229 if _, err = syntax.ParseATURI(record.Issue); err != nil {
230 return nil, err
231 }
232
233 i := record
234 mentions := make([]syntax.DID, len(record.Mentions))
235 for i, did := range record.Mentions {
236 mentions[i] = syntax.DID(did)
237 }
238 references := make([]syntax.ATURI, len(record.References))
239 for i, uri := range i.References {
240 references[i] = syntax.ATURI(uri)
241 }
242
243 comment := IssueComment{
244 Did: ownerDid,
245 Rkey: rkey,
246 Body: record.Body,
247 IssueAt: record.Issue,
248 ReplyTo: record.ReplyTo,
249 Created: created,
250 Mentions: mentions,
251 References: references,
252 }
253
254 return &comment, nil
255}