This repository has no description
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 return 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}
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() []string {
135 participantSet := make(map[string]struct{})
136 participants := []string{}
137
138 addParticipant := func(did string) {
139 if _, exists := participantSet[did]; !exists {
140 participantSet[did] = struct{}{}
141 participants = append(participants, did)
142 }
143 }
144
145 addParticipant(i.Did)
146
147 for _, c := range i.Comments {
148 addParticipant(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 var repoAt syntax.ATURI
166 if record.Repo != nil {
167 repoAt = syntax.ATURI(*record.Repo)
168 }
169
170 return Issue{
171 RepoAt: repoAt,
172 Did: did,
173 Rkey: rkey,
174 Created: created,
175 Title: record.Title,
176 Body: body,
177 Open: true, // new issues are open by default
178 }
179}
180
181type IssueComment struct {
182 Id int64
183 Did string
184 Rkey string
185 IssueAt string
186 ReplyTo *string
187 Body string
188 Created time.Time
189 Edited *time.Time
190 Deleted *time.Time
191 Mentions []syntax.DID
192 References []syntax.ATURI
193}
194
195func (i *IssueComment) AtUri() syntax.ATURI {
196 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
197}
198
199func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
200 mentions := make([]string, len(i.Mentions))
201 for i, did := range i.Mentions {
202 mentions[i] = string(did)
203 }
204 references := make([]string, len(i.References))
205 for i, uri := range i.References {
206 references[i] = string(uri)
207 }
208 return tangled.RepoIssueComment{
209 Body: i.Body,
210 Issue: i.IssueAt,
211 CreatedAt: i.Created.Format(time.RFC3339),
212 ReplyTo: i.ReplyTo,
213 Mentions: mentions,
214 References: references,
215 }
216}
217
218func (i *IssueComment) IsTopLevel() bool {
219 return i.ReplyTo == nil
220}
221
222func (i *IssueComment) IsReply() bool {
223 return i.ReplyTo != nil
224}
225
226func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
227 created, err := time.Parse(time.RFC3339, record.CreatedAt)
228 if err != nil {
229 created = time.Now()
230 }
231
232 ownerDid := did
233
234 if _, err = syntax.ParseATURI(record.Issue); err != nil {
235 return nil, err
236 }
237
238 i := record
239 mentions := make([]syntax.DID, len(record.Mentions))
240 for i, did := range record.Mentions {
241 mentions[i] = syntax.DID(did)
242 }
243 references := make([]syntax.ATURI, len(record.References))
244 for i, uri := range i.References {
245 references[i] = syntax.ATURI(uri)
246 }
247
248 comment := IssueComment{
249 Did: ownerDid,
250 Rkey: rkey,
251 Body: record.Body,
252 IssueAt: record.Issue,
253 ReplyTo: record.ReplyTo,
254 Created: created,
255 Mentions: mentions,
256 References: references,
257 }
258
259 return &comment, nil
260}