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