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 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 return tangled.RepoIssue{ 48 Repo: i.RepoAt.String(), 49 Title: i.Title, 50 Body: &i.Body, 51 Mentions: mentions, 52 References: references, 53 CreatedAt: i.Created.Format(time.RFC3339), 54 } 55} 56 57func (i *Issue) State() string { 58 if i.Open { 59 return "open" 60 } 61 return "closed" 62} 63 64type CommentListItem struct { 65 Self *IssueComment 66 Replies []*IssueComment 67} 68 69func (it *CommentListItem) Participants() []syntax.DID { 70 participantSet := make(map[syntax.DID]struct{}) 71 participants := []syntax.DID{} 72 73 addParticipant := func(did syntax.DID) { 74 if _, exists := participantSet[did]; !exists { 75 participantSet[did] = struct{}{} 76 participants = append(participants, did) 77 } 78 } 79 80 addParticipant(syntax.DID(it.Self.Did)) 81 82 for _, c := range it.Replies { 83 addParticipant(syntax.DID(c.Did)) 84 } 85 86 return participants 87} 88 89func (i *Issue) CommentList() []CommentListItem { 90 // Create a map to quickly find comments by their aturi 91 toplevel := make(map[string]*CommentListItem) 92 var replies []*IssueComment 93 94 // collect top level comments into the map 95 for _, comment := range i.Comments { 96 if comment.IsTopLevel() { 97 toplevel[comment.AtUri().String()] = &CommentListItem{ 98 Self: &comment, 99 } 100 } else { 101 replies = append(replies, &comment) 102 } 103 } 104 105 for _, r := range replies { 106 parentAt := *r.ReplyTo 107 if parent, exists := toplevel[parentAt]; exists { 108 parent.Replies = append(parent.Replies, r) 109 } 110 } 111 112 var listing []CommentListItem 113 for _, v := range toplevel { 114 listing = append(listing, *v) 115 } 116 117 // sort everything 118 sortFunc := func(a, b *IssueComment) bool { 119 return a.Created.Before(b.Created) 120 } 121 sort.Slice(listing, func(i, j int) bool { 122 return sortFunc(listing[i].Self, listing[j].Self) 123 }) 124 for _, r := range listing { 125 sort.Slice(r.Replies, func(i, j int) bool { 126 return sortFunc(r.Replies[i], r.Replies[j]) 127 }) 128 } 129 130 return listing 131} 132 133func (i *Issue) Participants() []string { 134 participantSet := make(map[string]struct{}) 135 participants := []string{} 136 137 addParticipant := func(did string) { 138 if _, exists := participantSet[did]; !exists { 139 participantSet[did] = struct{}{} 140 participants = append(participants, did) 141 } 142 } 143 144 addParticipant(i.Did) 145 146 for _, c := range i.Comments { 147 addParticipant(c.Did) 148 } 149 150 return participants 151} 152 153func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue { 154 created, err := time.Parse(time.RFC3339, record.CreatedAt) 155 if err != nil { 156 created = time.Now() 157 } 158 159 body := "" 160 if record.Body != nil { 161 body = *record.Body 162 } 163 164 return Issue{ 165 RepoAt: syntax.ATURI(record.Repo), 166 Did: did, 167 Rkey: rkey, 168 Created: created, 169 Title: record.Title, 170 Body: body, 171 Open: true, // new issues are open by default 172 } 173} 174 175type IssueComment struct { 176 Id int64 177 Did string 178 Rkey string 179 IssueAt string 180 ReplyTo *string 181 Body string 182 Created time.Time 183 Edited *time.Time 184 Deleted *time.Time 185 Mentions []syntax.DID 186 References []syntax.ATURI 187} 188 189func (i *IssueComment) AtUri() syntax.ATURI { 190 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey)) 191} 192 193func (i *IssueComment) AsRecord() tangled.RepoIssueComment { 194 mentions := make([]string, len(i.Mentions)) 195 for i, did := range i.Mentions { 196 mentions[i] = string(did) 197 } 198 references := make([]string, len(i.References)) 199 for i, uri := range i.References { 200 references[i] = string(uri) 201 } 202 return tangled.RepoIssueComment{ 203 Body: i.Body, 204 Issue: i.IssueAt, 205 CreatedAt: i.Created.Format(time.RFC3339), 206 ReplyTo: i.ReplyTo, 207 Mentions: mentions, 208 References: references, 209 } 210} 211 212func (i *IssueComment) IsTopLevel() bool { 213 return i.ReplyTo == nil 214} 215 216func (i *IssueComment) IsReply() bool { 217 return i.ReplyTo != nil 218} 219 220func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) { 221 created, err := time.Parse(time.RFC3339, record.CreatedAt) 222 if err != nil { 223 created = time.Now() 224 } 225 226 ownerDid := did 227 228 if _, err = syntax.ParseATURI(record.Issue); err != nil { 229 return nil, err 230 } 231 232 i := record 233 mentions := make([]syntax.DID, len(record.Mentions)) 234 for i, did := range record.Mentions { 235 mentions[i] = syntax.DID(did) 236 } 237 references := make([]syntax.ATURI, len(record.References)) 238 for i, uri := range i.References { 239 references[i] = syntax.ATURI(uri) 240 } 241 242 comment := IssueComment{ 243 Did: ownerDid, 244 Rkey: rkey, 245 Body: record.Body, 246 IssueAt: record.Issue, 247 ReplyTo: record.ReplyTo, 248 Created: created, 249 Mentions: mentions, 250 References: references, 251 } 252 253 return &comment, nil 254}