forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}