+165
appview/repo/feed.go
+165
appview/repo/feed.go
···
···
1
+
package repo
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"log"
7
+
"net/http"
8
+
"slices"
9
+
"time"
10
+
11
+
"tangled.sh/tangled.sh/core/appview/db"
12
+
"tangled.sh/tangled.sh/core/appview/reporesolver"
13
+
14
+
"github.com/bluesky-social/indigo/atproto/syntax"
15
+
"github.com/gorilla/feeds"
16
+
)
17
+
18
+
func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) {
19
+
const feedLimitPerType = 100
20
+
21
+
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))
22
+
if err != nil {
23
+
return nil, err
24
+
}
25
+
26
+
issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))
27
+
if err != nil {
28
+
return nil, err
29
+
}
30
+
31
+
feed := &feeds.Feed{
32
+
Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()),
33
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"},
34
+
Items: make([]*feeds.Item, 0),
35
+
Updated: time.UnixMilli(0),
36
+
}
37
+
38
+
for _, pull := range pulls {
39
+
items, err := rp.createPullItems(ctx, pull, f)
40
+
if err != nil {
41
+
return nil, err
42
+
}
43
+
feed.Items = append(feed.Items, items...)
44
+
}
45
+
46
+
for _, issue := range issues {
47
+
item, err := rp.createIssueItem(ctx, issue, f)
48
+
if err != nil {
49
+
return nil, err
50
+
}
51
+
feed.Items = append(feed.Items, item)
52
+
}
53
+
54
+
slices.SortFunc(feed.Items, func(a, b *feeds.Item) int {
55
+
if a.Created.After(b.Created) {
56
+
return -1
57
+
}
58
+
return 1
59
+
})
60
+
61
+
if len(feed.Items) > 0 {
62
+
feed.Updated = feed.Items[0].Created
63
+
}
64
+
65
+
return feed, nil
66
+
}
67
+
68
+
func (rp *Repo) createPullItems(ctx context.Context, pull *db.Pull, f *reporesolver.ResolvedRepo) ([]*feeds.Item, error) {
69
+
owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid)
70
+
if err != nil {
71
+
return nil, err
72
+
}
73
+
74
+
var items []*feeds.Item
75
+
76
+
state := rp.getPullState(pull)
77
+
description := rp.buildPullDescription(owner.Handle, state, pull, f.OwnerSlashRepo())
78
+
79
+
mainItem := &feeds.Item{
80
+
Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title),
81
+
Description: description,
82
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)},
83
+
Created: pull.Created,
84
+
Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)},
85
+
}
86
+
items = append(items, mainItem)
87
+
88
+
for _, round := range pull.Submissions {
89
+
if round == nil || round.RoundNumber == 0 {
90
+
continue
91
+
}
92
+
93
+
roundItem := &feeds.Item{
94
+
Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber),
95
+
Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()),
96
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)},
97
+
Created: round.Created,
98
+
Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)},
99
+
}
100
+
items = append(items, roundItem)
101
+
}
102
+
103
+
return items, nil
104
+
}
105
+
106
+
func (rp *Repo) createIssueItem(ctx context.Context, issue db.Issue, f *reporesolver.ResolvedRepo) (*feeds.Item, error) {
107
+
owner, err := rp.idResolver.ResolveIdent(ctx, issue.OwnerDid)
108
+
if err != nil {
109
+
return nil, err
110
+
}
111
+
112
+
state := "closed"
113
+
if issue.Open {
114
+
state = "opened"
115
+
}
116
+
117
+
return &feeds.Item{
118
+
Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title),
119
+
Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()),
120
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)},
121
+
Created: issue.Created,
122
+
Author: &feeds.Author{Name: fmt.Sprintf("@%s", owner.Handle)},
123
+
}, nil
124
+
}
125
+
126
+
func (rp *Repo) getPullState(pull *db.Pull) string {
127
+
if pull.State == db.PullOpen {
128
+
return "opened"
129
+
}
130
+
return pull.State.String()
131
+
}
132
+
133
+
func (rp *Repo) buildPullDescription(handle syntax.Handle, state string, pull *db.Pull, repoName string) string {
134
+
base := fmt.Sprintf("@%s %s pull request #%d", handle, state, pull.PullId)
135
+
136
+
if pull.State == db.PullMerged {
137
+
return fmt.Sprintf("%s (on round #%d) in %s", base, pull.LastRoundNumber(), repoName)
138
+
}
139
+
140
+
return fmt.Sprintf("%s in %s", base, repoName)
141
+
}
142
+
143
+
func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) {
144
+
f, err := rp.repoResolver.Resolve(r)
145
+
if err != nil {
146
+
log.Println("failed to fully resolve repo:", err)
147
+
return
148
+
}
149
+
150
+
feed, err := rp.getRepoFeed(r.Context(), f)
151
+
if err != nil {
152
+
log.Println("failed to get repo feed:", err)
153
+
rp.pages.Error500(w)
154
+
return
155
+
}
156
+
157
+
atom, err := feed.ToAtom()
158
+
if err != nil {
159
+
rp.pages.Error500(w)
160
+
return
161
+
}
162
+
163
+
w.Header().Set("content-type", "application/atom+xml")
164
+
w.Write([]byte(atom))
165
+
}
-122
appview/repo/repo.go
-122
appview/repo/repo.go
···
37
securejoin "github.com/cyphar/filepath-securejoin"
38
"github.com/go-chi/chi/v5"
39
"github.com/go-git/go-git/v5/plumbing"
40
-
"github.com/gorilla/feeds"
41
42
comatproto "github.com/bluesky-social/indigo/api/atproto"
43
"github.com/bluesky-social/indigo/atproto/syntax"
···
287
})
288
return
289
}
290
-
}
291
-
292
-
func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) {
293
-
const feedLimitPerType = 100
294
-
295
-
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))
296
-
if err != nil {
297
-
return nil, err
298
-
}
299
-
300
-
issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))
301
-
if err != nil {
302
-
return nil, err
303
-
}
304
-
305
-
feed := &feeds.Feed{
306
-
Title: fmt.Sprintf("activity feed for %s", f.OwnerSlashRepo()),
307
-
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"},
308
-
Items: make([]*feeds.Item, 0),
309
-
Updated: time.UnixMilli(0),
310
-
}
311
-
312
-
for _, pull := range pulls {
313
-
owner, err := rp.idResolver.ResolveIdent(ctx, pull.OwnerDid)
314
-
if err != nil {
315
-
return nil, err
316
-
}
317
-
318
-
var state string
319
-
if pull.State == db.PullOpen {
320
-
state = "opened"
321
-
} else {
322
-
state = pull.State.String()
323
-
}
324
-
mergedAtRounds := ""
325
-
if pull.State == db.PullMerged {
326
-
mergedAtRounds = fmt.Sprintf(" (on round #%d)", pull.LastRoundNumber())
327
-
}
328
-
item := &feeds.Item{
329
-
Title: fmt.Sprintf("[PR #%d] %s", pull.PullId, pull.Title),
330
-
Description: fmt.Sprintf("@%s %s pull request #%d%s in %s", owner.Handle, state, pull.PullId, mergedAtRounds, f.OwnerSlashRepo()),
331
-
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)},
332
-
Created: pull.Created,
333
-
Author: &feeds.Author{
334
-
Name: fmt.Sprintf("@%s", owner.Handle),
335
-
},
336
-
}
337
-
feed.Items = append(feed.Items, item)
338
-
339
-
for _, round := range pull.Submissions {
340
-
if round == nil || round.RoundNumber == 0 {
341
-
continue
342
-
}
343
-
item := &feeds.Item{
344
-
Title: fmt.Sprintf("[PR #%d] %s (round #%d)", pull.PullId, pull.Title, round.RoundNumber),
345
-
Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()),
346
-
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)},
347
-
Created: round.Created,
348
-
Author: &feeds.Author{
349
-
Name: fmt.Sprintf("@%s", owner.Handle),
350
-
},
351
-
}
352
-
feed.Items = append(feed.Items, item)
353
-
}
354
-
}
355
-
356
-
for _, issue := range issues {
357
-
owner, err := rp.idResolver.ResolveIdent(ctx, issue.OwnerDid)
358
-
if err != nil {
359
-
return nil, err
360
-
}
361
-
var state string
362
-
if issue.Open {
363
-
state = "opened"
364
-
} else {
365
-
state = "closed"
366
-
}
367
-
item := &feeds.Item{
368
-
Title: fmt.Sprintf("[Issue #%d] %s", issue.IssueId, issue.Title),
369
-
Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()),
370
-
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)},
371
-
Created: issue.Created,
372
-
Author: &feeds.Author{
373
-
Name: fmt.Sprintf("@%s", owner.Handle),
374
-
},
375
-
}
376
-
feed.Items = append(feed.Items, item)
377
-
}
378
-
379
-
slices.SortFunc(feed.Items, func(a *feeds.Item, b *feeds.Item) int {
380
-
return int(b.Created.UnixMilli()) - int(a.Created.UnixMilli())
381
-
})
382
-
if len(feed.Items) > 0 {
383
-
feed.Updated = feed.Items[0].Created
384
-
}
385
-
386
-
return feed, nil
387
-
}
388
-
389
-
func (rp *Repo) RepoAtomFeed(w http.ResponseWriter, r *http.Request) {
390
-
f, err := rp.repoResolver.Resolve(r)
391
-
if err != nil {
392
-
log.Println("failed to fully resolve repo:", err)
393
-
return
394
-
}
395
-
396
-
feed, err := rp.getRepoFeed(r.Context(), f)
397
-
if err != nil {
398
-
log.Println("failed to get repo feed:", err)
399
-
rp.pages.Error500(w)
400
-
return
401
-
}
402
-
403
-
atom, err := feed.ToAtom()
404
-
if err != nil {
405
-
rp.pages.Error500(w)
406
-
return
407
-
}
408
-
409
-
w.Header().Set("content-type", "application/atom+xml")
410
-
w.Write([]byte(atom))
411
}
412
413
func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {
···
37
securejoin "github.com/cyphar/filepath-securejoin"
38
"github.com/go-chi/chi/v5"
39
"github.com/go-git/go-git/v5/plumbing"
40
41
comatproto "github.com/bluesky-social/indigo/api/atproto"
42
"github.com/bluesky-social/indigo/atproto/syntax"
···
286
})
287
return
288
}
289
}
290
291
func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {