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 (i *Issue) CommentList() []CommentListItem {
58 // Create a map to quickly find comments by their aturi
59 toplevel := make(map[string]*CommentListItem)
60 var replies []*IssueComment
61
62 // collect top level comments into the map
63 for _, comment := range i.Comments {
64 if comment.IsTopLevel() {
65 toplevel[comment.AtUri().String()] = &CommentListItem{
66 Self: &comment,
67 }
68 } else {
69 replies = append(replies, &comment)
70 }
71 }
72
73 for _, r := range replies {
74 parentAt := *r.ReplyTo
75 if parent, exists := toplevel[parentAt]; exists {
76 parent.Replies = append(parent.Replies, r)
77 }
78 }
79
80 var listing []CommentListItem
81 for _, v := range toplevel {
82 listing = append(listing, *v)
83 }
84
85 // sort everything
86 sortFunc := func(a, b *IssueComment) bool {
87 return a.Created.Before(b.Created)
88 }
89 sort.Slice(listing, func(i, j int) bool {
90 return sortFunc(listing[i].Self, listing[j].Self)
91 })
92 for _, r := range listing {
93 sort.Slice(r.Replies, func(i, j int) bool {
94 return sortFunc(r.Replies[i], r.Replies[j])
95 })
96 }
97
98 return listing
99}
100
101func (i *Issue) Participants() []string {
102 participantSet := make(map[string]struct{})
103 participants := []string{}
104
105 addParticipant := func(did string) {
106 if _, exists := participantSet[did]; !exists {
107 participantSet[did] = struct{}{}
108 participants = append(participants, did)
109 }
110 }
111
112 addParticipant(i.Did)
113
114 for _, c := range i.Comments {
115 addParticipant(c.Did)
116 }
117
118 return participants
119}
120
121func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue {
122 created, err := time.Parse(time.RFC3339, record.CreatedAt)
123 if err != nil {
124 created = time.Now()
125 }
126
127 body := ""
128 if record.Body != nil {
129 body = *record.Body
130 }
131
132 return Issue{
133 RepoAt: syntax.ATURI(record.Repo),
134 Did: did,
135 Rkey: rkey,
136 Created: created,
137 Title: record.Title,
138 Body: body,
139 Open: true, // new issues are open by default
140 }
141}
142
143type IssueComment struct {
144 Id int64
145 Did string
146 Rkey string
147 IssueAt string
148 ReplyTo *string
149 Body string
150 Created time.Time
151 Edited *time.Time
152 Deleted *time.Time
153}
154
155func (i *IssueComment) AtUri() syntax.ATURI {
156 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
157}
158
159func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
160 return tangled.RepoIssueComment{
161 Body: i.Body,
162 Issue: i.IssueAt,
163 CreatedAt: i.Created.Format(time.RFC3339),
164 ReplyTo: i.ReplyTo,
165 }
166}
167
168func (i *IssueComment) IsTopLevel() bool {
169 return i.ReplyTo == nil
170}
171
172func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
173 created, err := time.Parse(time.RFC3339, record.CreatedAt)
174 if err != nil {
175 created = time.Now()
176 }
177
178 ownerDid := did
179
180 if _, err = syntax.ParseATURI(record.Issue); err != nil {
181 return nil, err
182 }
183
184 comment := IssueComment{
185 Did: ownerDid,
186 Rkey: rkey,
187 Body: record.Body,
188 IssueAt: record.Issue,
189 ReplyTo: record.ReplyTo,
190 Created: created,
191 }
192
193 return &comment, nil
194}