forked from
tangled.org/core
Monorepo for Tangled
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 RepoAt syntax.ATURI
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 repoAtStr := i.RepoAt.String()
48 rec := tangled.RepoIssue{
49 Repo: &repoAtStr,
50 Title: i.Title,
51 Body: &i.Body,
52 Mentions: mentions,
53 References: references,
54 CreatedAt: i.Created.Format(time.RFC3339),
55 }
56 if i.Repo != nil && i.Repo.RepoDid != "" {
57 rec.RepoDid = &i.Repo.RepoDid
58 }
59 return rec
60}
61
62func (i *Issue) State() string {
63 if i.Open {
64 return "open"
65 }
66 return "closed"
67}
68
69type CommentListItem struct {
70 Self *IssueComment
71 Replies []*IssueComment
72}
73
74func (it *CommentListItem) Participants() []syntax.DID {
75 participantSet := make(map[syntax.DID]struct{})
76 participants := []syntax.DID{}
77
78 addParticipant := func(did syntax.DID) {
79 if _, exists := participantSet[did]; !exists {
80 participantSet[did] = struct{}{}
81 participants = append(participants, did)
82 }
83 }
84
85 addParticipant(syntax.DID(it.Self.Did))
86
87 for _, c := range it.Replies {
88 addParticipant(syntax.DID(c.Did))
89 }
90
91 return participants
92}
93
94func (i *Issue) CommentList() []CommentListItem {
95 // Create a map to quickly find comments by their aturi
96 toplevel := make(map[string]*CommentListItem)
97 var replies []*IssueComment
98
99 // collect top level comments into the map
100 for _, comment := range i.Comments {
101 if comment.IsTopLevel() {
102 toplevel[comment.AtUri().String()] = &CommentListItem{
103 Self: &comment,
104 }
105 } else {
106 replies = append(replies, &comment)
107 }
108 }
109
110 for _, r := range replies {
111 parentAt := *r.ReplyTo
112 if parent, exists := toplevel[parentAt]; exists {
113 parent.Replies = append(parent.Replies, r)
114 }
115 }
116
117 var listing []CommentListItem
118 for _, v := range toplevel {
119 listing = append(listing, *v)
120 }
121
122 // sort everything
123 sortFunc := func(a, b *IssueComment) bool {
124 return a.Created.Before(b.Created)
125 }
126 sort.Slice(listing, func(i, j int) bool {
127 return sortFunc(listing[i].Self, listing[j].Self)
128 })
129 for _, r := range listing {
130 sort.Slice(r.Replies, func(i, j int) bool {
131 return sortFunc(r.Replies[i], r.Replies[j])
132 })
133 }
134
135 return listing
136}
137
138func (i *Issue) Participants() []string {
139 participantSet := make(map[string]struct{})
140 participants := []string{}
141
142 addParticipant := func(did string) {
143 if _, exists := participantSet[did]; !exists {
144 participantSet[did] = struct{}{}
145 participants = append(participants, did)
146 }
147 }
148
149 addParticipant(i.Did)
150
151 for _, c := range i.Comments {
152 addParticipant(c.Did)
153 }
154
155 return participants
156}
157
158func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue {
159 created, err := time.Parse(time.RFC3339, record.CreatedAt)
160 if err != nil {
161 created = time.Now()
162 }
163
164 body := ""
165 if record.Body != nil {
166 body = *record.Body
167 }
168
169 var repoAt syntax.ATURI
170 if record.Repo != nil {
171 repoAt = syntax.ATURI(*record.Repo)
172 }
173
174 return Issue{
175 RepoAt: repoAt,
176 Did: did,
177 Rkey: rkey,
178 Created: created,
179 Title: record.Title,
180 Body: body,
181 Open: true, // new issues are open by default
182 }
183}
184
185type IssueComment struct {
186 Id int64
187 Did string
188 Rkey string
189 IssueAt string
190 ReplyTo *string
191 Body string
192 Created time.Time
193 Edited *time.Time
194 Deleted *time.Time
195 Mentions []syntax.DID
196 References []syntax.ATURI
197}
198
199func (i *IssueComment) AtUri() syntax.ATURI {
200 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
201}
202
203func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
204 mentions := make([]string, len(i.Mentions))
205 for i, did := range i.Mentions {
206 mentions[i] = string(did)
207 }
208 references := make([]string, len(i.References))
209 for i, uri := range i.References {
210 references[i] = string(uri)
211 }
212 return tangled.RepoIssueComment{
213 Body: i.Body,
214 Issue: i.IssueAt,
215 CreatedAt: i.Created.Format(time.RFC3339),
216 ReplyTo: i.ReplyTo,
217 Mentions: mentions,
218 References: references,
219 }
220}
221
222func (i *IssueComment) IsTopLevel() bool {
223 return i.ReplyTo == nil
224}
225
226func (i *IssueComment) IsReply() bool {
227 return i.ReplyTo != nil
228}
229
230func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
231 created, err := time.Parse(time.RFC3339, record.CreatedAt)
232 if err != nil {
233 created = time.Now()
234 }
235
236 ownerDid := did
237
238 if _, err = syntax.ParseATURI(record.Issue); err != nil {
239 return nil, err
240 }
241
242 i := record
243 mentions := make([]syntax.DID, len(record.Mentions))
244 for i, did := range record.Mentions {
245 mentions[i] = syntax.DID(did)
246 }
247 references := make([]syntax.ATURI, len(record.References))
248 for i, uri := range i.References {
249 references[i] = syntax.ATURI(uri)
250 }
251
252 comment := IssueComment{
253 Did: ownerDid,
254 Rkey: rkey,
255 Body: record.Body,
256 IssueAt: record.Issue,
257 ReplyTo: record.ReplyTo,
258 Created: created,
259 Mentions: mentions,
260 References: references,
261 }
262
263 return &comment, nil
264}