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