forked from
tangled.org/core
Monorepo for Tangled — https://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 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
25 // optionally, populate this when querying for reverse mappings
26 // like comment counts, parent repo etc.
27 Comments []IssueComment
28 Labels LabelState
29 Repo *Repo
30}
31
32func (i *Issue) AtUri() syntax.ATURI {
33 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueNSID, i.Rkey))
34}
35
36func (i *Issue) AsRecord() tangled.RepoIssue {
37 return tangled.RepoIssue{
38 Repo: i.RepoAt.String(),
39 Title: i.Title,
40 Body: &i.Body,
41 CreatedAt: i.Created.Format(time.RFC3339),
42 }
43}
44
45func (i *Issue) State() string {
46 if i.Open {
47 return "open"
48 }
49 return "closed"
50}
51
52type CommentListItem struct {
53 Self *IssueComment
54 Replies []*IssueComment
55}
56
57func (it *CommentListItem) Participants() []syntax.DID {
58 participantSet := make(map[syntax.DID]struct{})
59 participants := []syntax.DID{}
60
61 addParticipant := func(did syntax.DID) {
62 if _, exists := participantSet[did]; !exists {
63 participantSet[did] = struct{}{}
64 participants = append(participants, did)
65 }
66 }
67
68 addParticipant(syntax.DID(it.Self.Did))
69
70 for _, c := range it.Replies {
71 addParticipant(syntax.DID(c.Did))
72 }
73
74 return participants
75}
76
77func (i *Issue) CommentList() []CommentListItem {
78 // Create a map to quickly find comments by their aturi
79 toplevel := make(map[string]*CommentListItem)
80 var replies []*IssueComment
81
82 // collect top level comments into the map
83 for _, comment := range i.Comments {
84 if comment.IsTopLevel() {
85 toplevel[comment.AtUri().String()] = &CommentListItem{
86 Self: &comment,
87 }
88 } else {
89 replies = append(replies, &comment)
90 }
91 }
92
93 for _, r := range replies {
94 parentAt := *r.ReplyTo
95 if parent, exists := toplevel[parentAt]; exists {
96 parent.Replies = append(parent.Replies, r)
97 }
98 }
99
100 var listing []CommentListItem
101 for _, v := range toplevel {
102 listing = append(listing, *v)
103 }
104
105 // sort everything
106 sortFunc := func(a, b *IssueComment) bool {
107 return a.Created.Before(b.Created)
108 }
109 sort.Slice(listing, func(i, j int) bool {
110 return sortFunc(listing[i].Self, listing[j].Self)
111 })
112 for _, r := range listing {
113 sort.Slice(r.Replies, func(i, j int) bool {
114 return sortFunc(r.Replies[i], r.Replies[j])
115 })
116 }
117
118 return listing
119}
120
121func (i *Issue) Participants() []string {
122 participantSet := make(map[string]struct{})
123 participants := []string{}
124
125 addParticipant := func(did string) {
126 if _, exists := participantSet[did]; !exists {
127 participantSet[did] = struct{}{}
128 participants = append(participants, did)
129 }
130 }
131
132 addParticipant(i.Did)
133
134 for _, c := range i.Comments {
135 addParticipant(c.Did)
136 }
137
138 return participants
139}
140
141func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue {
142 created, err := time.Parse(time.RFC3339, record.CreatedAt)
143 if err != nil {
144 created = time.Now()
145 }
146
147 body := ""
148 if record.Body != nil {
149 body = *record.Body
150 }
151
152 return Issue{
153 RepoAt: syntax.ATURI(record.Repo),
154 Did: did,
155 Rkey: rkey,
156 Created: created,
157 Title: record.Title,
158 Body: body,
159 Open: true, // new issues are open by default
160 }
161}
162
163type IssueComment struct {
164 Id int64
165 Did string
166 Rkey string
167 IssueAt string
168 ReplyTo *string
169 Body string
170 Created time.Time
171 Edited *time.Time
172 Deleted *time.Time
173}
174
175func (i *IssueComment) AtUri() syntax.ATURI {
176 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
177}
178
179func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
180 return tangled.RepoIssueComment{
181 Body: i.Body,
182 Issue: i.IssueAt,
183 CreatedAt: i.Created.Format(time.RFC3339),
184 ReplyTo: i.ReplyTo,
185 }
186}
187
188func (i *IssueComment) IsTopLevel() bool {
189 return i.ReplyTo == nil
190}
191
192func (i *IssueComment) IsReply() bool {
193 return i.ReplyTo != nil
194}
195
196func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
197 created, err := time.Parse(time.RFC3339, record.CreatedAt)
198 if err != nil {
199 created = time.Now()
200 }
201
202 ownerDid := did
203
204 if _, err = syntax.ParseATURI(record.Issue); err != nil {
205 return nil, err
206 }
207
208 comment := IssueComment{
209 Did: ownerDid,
210 Rkey: rkey,
211 Body: record.Body,
212 IssueAt: record.Issue,
213 ReplyTo: record.ReplyTo,
214 Created: created,
215 }
216
217 return &comment, nil
218}