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}