tangled
alpha
login
or
join now
serendipty01.dev
/
tangled-core
forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
0
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
txns
two-way-comms
tracing
three-easy-steps
test-pr-link
test-ci
tags-and-releases
summarize-rounds
stars
stacked-prs
spindle
settings-router
session-refresh
sandboxed-atmosphere
revert-telemetry
repotab-icons
repo-deletion
render-markup
remove-set-repo-at
release-artifacts
refactor-ks-client
reduce-dids
push-zxovstvplnok
push-zwoymssunvsp
push-zvuqkxuqzmlx
push-zvtqtworuppk
push-zvkrywwskknq
push-zunxqvqoqmoq
push-zrvypwzlqtvz
push-zrkqwwrtxvzn
push-zqrmrxxvrylu
push-zqqvxroypxvy
push-zomkuzmzokvl
push-znrkktvllqsp
push-znlsowpxwvvq
push-zlzkyrrnylxr
push-zklmrltkvxkq
push-yytstxoqlwmx
push-yynslowxntql
push-ywynwzosnrox
push-yuwoytzsmkvm
push-yulzrzunkznl
push-ytzxyplmvunn
push-ytvszwnnmymo
push-ytpxvpknlxun
push-ytkuzknmmrmn
push-yssxzpkyorwv
push-yqnqquktxqpx
push-ylvkxyvlmkyq
push-ykwytywspowp
push-xwxnuorpzwsu
push-xwotmtuuvokm
push-xvuvxolnykpl
push-xvrvnuvsmslv
push-xvnwyvuwwylt
push-xtzkpqpkmvvr
push-xtuxqqlytkws
push-xttmuzqzktwv
push-xsmwulupylxx
push-xopyykmxuqzw
push-xmplzvpqtnru
push-xlupzwtslltp
push-wzlvlnywrxrs
push-wzkqymznotmx
push-wyonpyyzurwl
push-wykupplnxytq
push-wxwpnnytwxsq
push-wxvwuxxzswmv
push-wvywrqlppxqo
push-wvwxxlyxrxlm
push-wukyptkosoux
push-wtmrprmomuus
push-wsxnywmtsnrp
push-wqzwpukkvnuw
push-wpkykovtqxnx
push-wnwpznvpzwqu
push-wnotmtoqlnvl
push-wmkmzrsvlkmk
push-wkwyolovuxvq
push-vyyrpsmynrwo
push-vyusnwqnmxwy
push-vynsusnqpmus
push-vwyomovpppwp
push-vwuuzxwwsvow
push-vvluoqoywsvp
push-vuzywsvmkwqn
push-vtunvupqotuo
push-vssvkowylwvx
push-vquoltwpkuny
push-vnxxyxursomy
push-vmxtqnzznxvk
push-vkywmxzkrnqx
push-uylxxnsupvlk
push-uwuymmkuwsrs
push-uwotwontksrz
push-uuyqkqmkmlsn
push-uruyyqupsmro
push-upnknovvnoqr
push-uotxnqrqnlyl
push-ummlkpsrvrvn
push-ukznmmplmlul
push-ukvwpkrpryvm
push-ukmxnzrlrpuo
push-tzyltlmtsznq
push-tyxzqwosmzuv
push-twskywooslyl
push-twnpklmstzor
push-twlxnytrpyuk
push-tvvnxqyykkwr
push-tvtwutsrtoxt
push-tultwuoxrmun
push-ttyzwxqtkpsl
push-tnwwtqxpoqpk
push-tlxunysxvxwk
push-tkxmuvxkylmy
push-tkusxvwknltw
push-synlttxvkzox
push-svtkmrzmrwky
push-stktrzvlltpw
push-sssuxsytslts
push-spvnpqlqqpkw
push-snyqozvrosmk
push-snktzuwttuvu
push-smsrusztuvwt
push-rvtqynpmozzy
push-rurrtsvmnvku
push-ruoqnsmttnxx
push-rtwrkkyllvkm
push-rtvxspsprvuv
push-rsrowlxunurk
push-rrsulrtnsmtw
push-rqzvvtnzkzvq
push-rqxyyurmpkps
push-rnztkppsvskw
push-rlorkkyzokzr
push-rlktrmqtttnn
push-qwnqkqnmovyn
push-qtrkvornpykm
push-qsmrouptzqkq
push-qruszvywputp
push-qrltzqmlrlln
push-qqwquvlysrxt
push-qqsspmnuxqwk
push-qqrowoolzppw
push-qoplqnlvlqqo
push-qnstkrpltlqu
push-qlzpkvltqlzm
push-qkpqsrknozxs
push-qkkmntrmxqly
push-pzwzzlwkkpnw
push-pzrysszkwwtk
push-pymywyqyumlo
push-pyllqzrprrny
push-pwqwlvnsqtqr
push-pvpnsqvvzuky
push-psqwruuwvwlo
push-prszwlupsoqz
push-prquzlvztlul
push-ppstsspzpxrx
push-potvrpwlpwsl
push-pnknzwntuuwx
push-pmtpswykplzr
push-plpuqtyrprou
push-plmznxvyqrqw
push-pkyzqzmqunnl
push-pkqzkqmxotyz
push-pkklqwlqwoml
push-owwtwvxyzowl
push-ovsuzpqkpmlt
push-ovrqrxnpvroz
push-ossmlnsnvstq
push-ormxrmmzomqu
push-oovokvlkywly
push-okymwlupkook
push-nwslswprzvmx
push-nwrnkztxkovl
push-nupxprsykpzq
push-nsolyvvsyvzw
push-nqrqutkzxskl
push-nozqtwvsrvkx
push-nnuvlyptxsqy
push-nmwononozzxz
push-nlvzxwtrmryl
push-nlnnyywnokyn
push-mzupsvxpvqvx
push-mzsvpkottnnt
push-myqmppunmplu
push-mwkwusmyymno
push-mvmrzuxwmzvs
push-mutuoxwuokqw
push-mtsxyxnkznyy
push-mtqkyzxrqnnn
push-msvlvnlwuyxy
push-mrwusmywonor
push-mqkxnymrqmzz
push-mozrrovxmlou
push-mopllyvuuuls
push-monplorvolvl
push-momltwttmuyq
push-mntstlmzqrvs
push-mmxkmyvyuzlk
push-mmrtnyoskwoo
push-mmnuoyuqplyk
push-mlxxvvqpzvlw
push-mllunlokmuxp
push-lzkzortpuyyq
push-lyrpkknpnrus
push-lxxtrqtnnoxy
push-lvuknrupsyox
push-lrzzmtxokrxw
push-lnvnxmxlqkux
push-lnupvsypkuow
push-llqkvunvvzyv
push-ktsnmppqsnls
push-kstnoynmspqp
push-ksrsmmytwuul
push-kslplloylmlp
push-kpmvvnzwyosv
push-kltomzxpyoxq
pull-from-fork-pill
pronouns
profile-timeline
profile-customizations
pr-actions
patch-requests
paginate-issues
packages-small
packages
oplog
opengraph
oauth
more-diff-stats
master
local-fragments
knot-xrpc
knot-cli
issues-edit
interdiff
improve-styling
improve-repo-name-checks
group-profile-timeline
format-patch
fork-repo
fork-pulls
fix-tw-dark
fix-resubmit
fix-refresh-issues
fix-knot-forks
fix-jetstream-bugs
fix-env-test
file-tree
fast-dev
enable-html
drop-at
diff-improvements
did-email-assoc
crash-repro-knot
consolidate-syntax-styles
compare-merge-base
commits-page
commit-verification
ci
camo
cache-busting
branch-prs-2
branch-prs
branch-fmt-patch
atprotate-pubkeys
all-repositories-page
add-migrations
v1.10.0-alpha
v1.9.1-alpha
v1.9.0-alpha
v1.8.1-alpha
v1.8.0-alpha
v1.7.0-alpha
v1.6.0-alpha
v1.5.0-alpha
v1.4.0-alpha
v1.3.0-alpha
v1.2.2-alpha
v1.2.1-alpha
v1.2.0-alpha
v1.1.2-alpha
v1.1.1-alpha
v1.1.0-alpha
v1.0.6-alpha
v1.0.5-alpha
v1.0.4-alpha
v1.0.3-alpha
v1.0.2-alpha
v1.0.1-alpha
v1.0.0-alpha
compare:
txns
two-way-comms
tracing
three-easy-steps
test-pr-link
test-ci
tags-and-releases
summarize-rounds
stars
stacked-prs
spindle
settings-router
session-refresh
sandboxed-atmosphere
revert-telemetry
repotab-icons
repo-deletion
render-markup
remove-set-repo-at
release-artifacts
refactor-ks-client
reduce-dids
push-zxovstvplnok
push-zwoymssunvsp
push-zvuqkxuqzmlx
push-zvtqtworuppk
push-zvkrywwskknq
push-zunxqvqoqmoq
push-zrvypwzlqtvz
push-zrkqwwrtxvzn
push-zqrmrxxvrylu
push-zqqvxroypxvy
push-zomkuzmzokvl
push-znrkktvllqsp
push-znlsowpxwvvq
push-zlzkyrrnylxr
push-zklmrltkvxkq
push-yytstxoqlwmx
push-yynslowxntql
push-ywynwzosnrox
push-yuwoytzsmkvm
push-yulzrzunkznl
push-ytzxyplmvunn
push-ytvszwnnmymo
push-ytpxvpknlxun
push-ytkuzknmmrmn
push-yssxzpkyorwv
push-yqnqquktxqpx
push-ylvkxyvlmkyq
push-ykwytywspowp
push-xwxnuorpzwsu
push-xwotmtuuvokm
push-xvuvxolnykpl
push-xvrvnuvsmslv
push-xvnwyvuwwylt
push-xtzkpqpkmvvr
push-xtuxqqlytkws
push-xttmuzqzktwv
push-xsmwulupylxx
push-xopyykmxuqzw
push-xmplzvpqtnru
push-xlupzwtslltp
push-wzlvlnywrxrs
push-wzkqymznotmx
push-wyonpyyzurwl
push-wykupplnxytq
push-wxwpnnytwxsq
push-wxvwuxxzswmv
push-wvywrqlppxqo
push-wvwxxlyxrxlm
push-wukyptkosoux
push-wtmrprmomuus
push-wsxnywmtsnrp
push-wqzwpukkvnuw
push-wpkykovtqxnx
push-wnwpznvpzwqu
push-wnotmtoqlnvl
push-wmkmzrsvlkmk
push-wkwyolovuxvq
push-vyyrpsmynrwo
push-vyusnwqnmxwy
push-vynsusnqpmus
push-vwyomovpppwp
push-vwuuzxwwsvow
push-vvluoqoywsvp
push-vuzywsvmkwqn
push-vtunvupqotuo
push-vssvkowylwvx
push-vquoltwpkuny
push-vnxxyxursomy
push-vmxtqnzznxvk
push-vkywmxzkrnqx
push-uylxxnsupvlk
push-uwuymmkuwsrs
push-uwotwontksrz
push-uuyqkqmkmlsn
push-uruyyqupsmro
push-upnknovvnoqr
push-uotxnqrqnlyl
push-ummlkpsrvrvn
push-ukznmmplmlul
push-ukvwpkrpryvm
push-ukmxnzrlrpuo
push-tzyltlmtsznq
push-tyxzqwosmzuv
push-twskywooslyl
push-twnpklmstzor
push-twlxnytrpyuk
push-tvvnxqyykkwr
push-tvtwutsrtoxt
push-tultwuoxrmun
push-ttyzwxqtkpsl
push-tnwwtqxpoqpk
push-tlxunysxvxwk
push-tkxmuvxkylmy
push-tkusxvwknltw
push-synlttxvkzox
push-svtkmrzmrwky
push-stktrzvlltpw
push-sssuxsytslts
push-spvnpqlqqpkw
push-snyqozvrosmk
push-snktzuwttuvu
push-smsrusztuvwt
push-rvtqynpmozzy
push-rurrtsvmnvku
push-ruoqnsmttnxx
push-rtwrkkyllvkm
push-rtvxspsprvuv
push-rsrowlxunurk
push-rrsulrtnsmtw
push-rqzvvtnzkzvq
push-rqxyyurmpkps
push-rnztkppsvskw
push-rlorkkyzokzr
push-rlktrmqtttnn
push-qwnqkqnmovyn
push-qtrkvornpykm
push-qsmrouptzqkq
push-qruszvywputp
push-qrltzqmlrlln
push-qqwquvlysrxt
push-qqsspmnuxqwk
push-qqrowoolzppw
push-qoplqnlvlqqo
push-qnstkrpltlqu
push-qlzpkvltqlzm
push-qkpqsrknozxs
push-qkkmntrmxqly
push-pzwzzlwkkpnw
push-pzrysszkwwtk
push-pymywyqyumlo
push-pyllqzrprrny
push-pwqwlvnsqtqr
push-pvpnsqvvzuky
push-psqwruuwvwlo
push-prszwlupsoqz
push-prquzlvztlul
push-ppstsspzpxrx
push-potvrpwlpwsl
push-pnknzwntuuwx
push-pmtpswykplzr
push-plpuqtyrprou
push-plmznxvyqrqw
push-pkyzqzmqunnl
push-pkqzkqmxotyz
push-pkklqwlqwoml
push-owwtwvxyzowl
push-ovsuzpqkpmlt
push-ovrqrxnpvroz
push-ossmlnsnvstq
push-ormxrmmzomqu
push-oovokvlkywly
push-okymwlupkook
push-nwslswprzvmx
push-nwrnkztxkovl
push-nupxprsykpzq
push-nsolyvvsyvzw
push-nqrqutkzxskl
push-nozqtwvsrvkx
push-nnuvlyptxsqy
push-nmwononozzxz
push-nlvzxwtrmryl
push-nlnnyywnokyn
push-mzupsvxpvqvx
push-mzsvpkottnnt
push-myqmppunmplu
push-mwkwusmyymno
push-mvmrzuxwmzvs
push-mutuoxwuokqw
push-mtsxyxnkznyy
push-mtqkyzxrqnnn
push-msvlvnlwuyxy
push-mrwusmywonor
push-mqkxnymrqmzz
push-mozrrovxmlou
push-mopllyvuuuls
push-monplorvolvl
push-momltwttmuyq
push-mntstlmzqrvs
push-mmxkmyvyuzlk
push-mmrtnyoskwoo
push-mmnuoyuqplyk
push-mlxxvvqpzvlw
push-mllunlokmuxp
push-lzkzortpuyyq
push-lyrpkknpnrus
push-lxxtrqtnnoxy
push-lvuknrupsyox
push-lrzzmtxokrxw
push-lnvnxmxlqkux
push-lnupvsypkuow
push-llqkvunvvzyv
push-ktsnmppqsnls
push-kstnoynmspqp
push-ksrsmmytwuul
push-kslplloylmlp
push-kpmvvnzwyosv
push-kltomzxpyoxq
pull-from-fork-pill
pronouns
profile-timeline
profile-customizations
pr-actions
patch-requests
paginate-issues
packages-small
packages
oplog
opengraph
oauth
more-diff-stats
master
local-fragments
knot-xrpc
knot-cli
issues-edit
interdiff
improve-styling
improve-repo-name-checks
group-profile-timeline
format-patch
fork-repo
fork-pulls
fix-tw-dark
fix-resubmit
fix-refresh-issues
fix-knot-forks
fix-jetstream-bugs
fix-env-test
file-tree
fast-dev
enable-html
drop-at
diff-improvements
did-email-assoc
crash-repro-knot
consolidate-syntax-styles
compare-merge-base
commits-page
commit-verification
ci
camo
cache-busting
branch-prs-2
branch-prs
branch-fmt-patch
atprotate-pubkeys
all-repositories-page
add-migrations
v1.10.0-alpha
v1.9.1-alpha
v1.9.0-alpha
v1.8.1-alpha
v1.8.0-alpha
v1.7.0-alpha
v1.6.0-alpha
v1.5.0-alpha
v1.4.0-alpha
v1.3.0-alpha
v1.2.2-alpha
v1.2.1-alpha
v1.2.0-alpha
v1.1.2-alpha
v1.1.1-alpha
v1.1.0-alpha
v1.0.6-alpha
v1.0.5-alpha
v1.0.4-alpha
v1.0.3-alpha
v1.0.2-alpha
v1.0.1-alpha
v1.0.0-alpha
go
+142
-88
22 changed files
expand all
collapse all
unified
split
appview
db
issues.go
notifications.go
issues
issues.go
middleware
middleware.go
notifications
notifications.go
notify
merged_notifier.go
oauth
handler.go
oauth.go
pages
funcmap.go
pages.go
templates
repo
issues
fragments
issueCommentHeader.html
issue.html
settings
access.html
spindles
fragments
addMemberModal.html
strings
string.html
user
fragments
followCard.html
login.html
pagination
page.go
state
follow.go
gfi.go
login.go
spindle
engines
nixery
engine.go
+8
-5
appview/db/issues.go
···
101
101
pLower := FilterGte("row_num", page.Offset+1)
102
102
pUpper := FilterLte("row_num", page.Offset+page.Limit)
103
103
104
104
-
args = append(args, pLower.Arg()...)
105
105
-
args = append(args, pUpper.Arg()...)
106
106
-
pagination := " where " + pLower.Condition() + " and " + pUpper.Condition()
104
104
+
pageClause := ""
105
105
+
if page.Limit > 0 {
106
106
+
args = append(args, pLower.Arg()...)
107
107
+
args = append(args, pUpper.Arg()...)
108
108
+
pageClause = " where " + pLower.Condition() + " and " + pUpper.Condition()
109
109
+
}
107
110
108
111
query := fmt.Sprintf(
109
112
`
···
128
131
%s
129
132
`,
130
133
whereClause,
131
131
-
pagination,
134
134
+
pageClause,
132
135
)
133
136
134
137
rows, err := e.Query(query, args...)
···
244
247
}
245
248
246
249
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
247
247
-
return GetIssuesPaginated(e, pagination.FirstPage(), filters...)
250
250
+
return GetIssuesPaginated(e, pagination.Page{}, filters...)
248
251
}
249
252
250
253
func AddIssueComment(e Execer, c models.IssueComment) (int64, error) {
+7
-4
appview/db/notifications.go
···
60
60
whereClause += " AND " + condition
61
61
}
62
62
}
63
63
+
pageClause := ""
64
64
+
if page.Limit > 0 {
65
65
+
pageClause = " limit ? offset ? "
66
66
+
args = append(args, page.Limit, page.Offset)
67
67
+
}
63
68
64
69
query := fmt.Sprintf(`
65
70
select id, recipient_did, actor_did, type, entity_type, entity_id, read, created, repo_id, issue_id, pull_id
66
71
from notifications
67
72
%s
68
73
order by created desc
69
69
-
limit ? offset ?
70
70
-
`, whereClause)
71
71
-
72
72
-
args = append(args, page.Limit, page.Offset)
74
74
+
%s
75
75
+
`, whereClause, pageClause)
73
76
74
77
rows, err := e.QueryContext(context.Background(), query, args...)
75
78
if err != nil {
+1
-5
appview/issues/issues.go
···
770
770
isOpen = true
771
771
}
772
772
773
773
-
page, ok := r.Context().Value("page").(pagination.Page)
774
774
-
if !ok {
775
775
-
l.Error("failed to get page")
776
776
-
page = pagination.FirstPage()
777
777
-
}
773
773
+
page := pagination.FromContext(r.Context())
778
774
779
775
user := rp.oauth.GetUser(r)
780
776
f, err := rp.repoResolver.Resolve(r)
+1
-1
appview/middleware/middleware.go
···
105
105
}
106
106
}
107
107
108
108
-
ctx := context.WithValue(r.Context(), "page", page)
108
108
+
ctx := pagination.IntoContext(r.Context(), page)
109
109
next.ServeHTTP(w, r.WithContext(ctx))
110
110
})
111
111
}
+1
-5
appview/notifications/notifications.go
···
49
49
l := n.logger.With("handler", "notificationsPage")
50
50
user := n.oauth.GetUser(r)
51
51
52
52
-
page, ok := r.Context().Value("page").(pagination.Page)
53
53
-
if !ok {
54
54
-
l.Error("failed to get page")
55
55
-
page = pagination.FirstPage()
56
56
-
}
52
52
+
page := pagination.FromContext(r.Context())
57
53
58
54
total, err := db.CountNotifications(
59
55
n.db,
+42
-50
appview/notify/merged_notifier.go
···
2
2
3
3
import (
4
4
"context"
5
5
+
"reflect"
6
6
+
"sync"
5
7
6
8
"tangled.org/core/appview/models"
7
9
)
···
16
18
17
19
var _ Notifier = &mergedNotifier{}
18
20
19
19
-
func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
20
20
-
for _, notifier := range m.notifiers {
21
21
-
notifier.NewRepo(ctx, repo)
21
21
+
// fanout calls the same method on all notifiers concurrently
22
22
+
func (m *mergedNotifier) fanout(method string, args ...any) {
23
23
+
var wg sync.WaitGroup
24
24
+
for _, n := range m.notifiers {
25
25
+
wg.Add(1)
26
26
+
go func(notifier Notifier) {
27
27
+
defer wg.Done()
28
28
+
v := reflect.ValueOf(notifier).MethodByName(method)
29
29
+
in := make([]reflect.Value, len(args))
30
30
+
for i, arg := range args {
31
31
+
in[i] = reflect.ValueOf(arg)
32
32
+
}
33
33
+
v.Call(in)
34
34
+
}(n)
22
35
}
36
36
+
wg.Wait()
37
37
+
}
38
38
+
39
39
+
func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
40
40
+
m.fanout("NewRepo", ctx, repo)
23
41
}
24
42
25
43
func (m *mergedNotifier) NewStar(ctx context.Context, star *models.Star) {
26
26
-
for _, notifier := range m.notifiers {
27
27
-
notifier.NewStar(ctx, star)
28
28
-
}
44
44
+
m.fanout("NewStar", ctx, star)
29
45
}
46
46
+
30
47
func (m *mergedNotifier) DeleteStar(ctx context.Context, star *models.Star) {
31
31
-
for _, notifier := range m.notifiers {
32
32
-
notifier.DeleteStar(ctx, star)
33
33
-
}
48
48
+
m.fanout("DeleteStar", ctx, star)
34
49
}
35
50
36
51
func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
37
37
-
for _, notifier := range m.notifiers {
38
38
-
notifier.NewIssue(ctx, issue)
39
39
-
}
52
52
+
m.fanout("NewIssue", ctx, issue)
40
53
}
54
54
+
41
55
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
42
42
-
for _, notifier := range m.notifiers {
43
43
-
notifier.NewIssueComment(ctx, comment)
44
44
-
}
56
56
+
m.fanout("NewIssueComment", ctx, comment)
45
57
}
46
58
47
59
func (m *mergedNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) {
48
48
-
for _, notifier := range m.notifiers {
49
49
-
notifier.NewIssueClosed(ctx, issue)
50
50
-
}
60
60
+
m.fanout("NewIssueClosed", ctx, issue)
51
61
}
52
62
53
63
func (m *mergedNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
54
54
-
for _, notifier := range m.notifiers {
55
55
-
notifier.NewFollow(ctx, follow)
56
56
-
}
64
64
+
m.fanout("NewFollow", ctx, follow)
57
65
}
66
66
+
58
67
func (m *mergedNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {
59
59
-
for _, notifier := range m.notifiers {
60
60
-
notifier.DeleteFollow(ctx, follow)
61
61
-
}
68
68
+
m.fanout("DeleteFollow", ctx, follow)
62
69
}
63
70
64
71
func (m *mergedNotifier) NewPull(ctx context.Context, pull *models.Pull) {
65
65
-
for _, notifier := range m.notifiers {
66
66
-
notifier.NewPull(ctx, pull)
67
67
-
}
72
72
+
m.fanout("NewPull", ctx, pull)
68
73
}
74
74
+
69
75
func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.PullComment) {
70
70
-
for _, notifier := range m.notifiers {
71
71
-
notifier.NewPullComment(ctx, comment)
72
72
-
}
76
76
+
m.fanout("NewPullComment", ctx, comment)
73
77
}
74
78
75
79
func (m *mergedNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
76
76
-
for _, notifier := range m.notifiers {
77
77
-
notifier.NewPullMerged(ctx, pull)
78
78
-
}
80
80
+
m.fanout("NewPullMerged", ctx, pull)
79
81
}
80
82
81
83
func (m *mergedNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
82
82
-
for _, notifier := range m.notifiers {
83
83
-
notifier.NewPullClosed(ctx, pull)
84
84
-
}
84
84
+
m.fanout("NewPullClosed", ctx, pull)
85
85
}
86
86
87
87
func (m *mergedNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {
88
88
-
for _, notifier := range m.notifiers {
89
89
-
notifier.UpdateProfile(ctx, profile)
90
90
-
}
88
88
+
m.fanout("UpdateProfile", ctx, profile)
91
89
}
92
90
93
93
-
func (m *mergedNotifier) NewString(ctx context.Context, string *models.String) {
94
94
-
for _, notifier := range m.notifiers {
95
95
-
notifier.NewString(ctx, string)
96
96
-
}
91
91
+
func (m *mergedNotifier) NewString(ctx context.Context, s *models.String) {
92
92
+
m.fanout("NewString", ctx, s)
97
93
}
98
94
99
99
-
func (m *mergedNotifier) EditString(ctx context.Context, string *models.String) {
100
100
-
for _, notifier := range m.notifiers {
101
101
-
notifier.EditString(ctx, string)
102
102
-
}
95
95
+
func (m *mergedNotifier) EditString(ctx context.Context, s *models.String) {
96
96
+
m.fanout("EditString", ctx, s)
103
97
}
104
98
105
99
func (m *mergedNotifier) DeleteString(ctx context.Context, did, rkey string) {
106
106
-
for _, notifier := range m.notifiers {
107
107
-
notifier.DeleteString(ctx, did, rkey)
108
108
-
}
100
100
+
m.fanout("DeleteString", ctx, did, rkey)
109
101
}
+13
-2
appview/oauth/handler.go
···
4
4
"bytes"
5
5
"context"
6
6
"encoding/json"
7
7
+
"errors"
7
8
"fmt"
8
9
"net/http"
9
10
"slices"
10
11
"time"
11
12
13
13
+
"github.com/bluesky-social/indigo/atproto/auth/oauth"
12
14
"github.com/go-chi/chi/v5"
13
15
"github.com/lestrrat-go/jwx/v2/jwk"
14
16
"github.com/posthog/posthog-go"
···
58
60
59
61
func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) {
60
62
ctx := r.Context()
63
63
+
l := o.Logger.With("query", r.URL.Query())
61
64
62
65
sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query())
63
66
if err != nil {
64
64
-
http.Error(w, err.Error(), http.StatusInternalServerError)
67
67
+
var callbackErr *oauth.AuthRequestCallbackError
68
68
+
if errors.As(err, &callbackErr) {
69
69
+
l.Debug("callback error", "err", callbackErr)
70
70
+
http.Redirect(w, r, fmt.Sprintf("/login?error=%s", callbackErr.ErrorCode), http.StatusFound)
71
71
+
return
72
72
+
}
73
73
+
l.Error("failed to process callback", "err", err)
74
74
+
http.Redirect(w, r, "/login?error=oauth", http.StatusFound)
65
75
return
66
76
}
67
77
68
78
if err := o.SaveSession(w, r, sessData); err != nil {
69
69
-
http.Error(w, err.Error(), http.StatusInternalServerError)
79
79
+
l.Error("failed to save session", "data", sessData, "err", err)
80
80
+
http.Redirect(w, r, "/login?error=session", http.StatusFound)
70
81
return
71
82
}
72
83
+4
-1
appview/oauth/oauth.go
···
58
58
59
59
sessStore := sessions.NewCookieStore([]byte(config.Core.CookieSecret))
60
60
61
61
+
clientApp := oauth.NewClientApp(&oauthConfig, authStore)
62
62
+
clientApp.Dir = res.Directory()
63
63
+
61
64
return &OAuth{
62
62
-
ClientApp: oauth.NewClientApp(&oauthConfig, authStore),
65
65
+
ClientApp: clientApp,
63
66
Config: config,
64
67
SessStore: sessStore,
65
68
JwksUri: jwksUri,
+3
-2
appview/pages/funcmap.go
···
297
297
},
298
298
299
299
"normalizeForHtmlId": func(s string) string {
300
300
-
// TODO: extend this to handle other cases?
301
301
-
return strings.ReplaceAll(s, ":", "_")
300
300
+
normalized := strings.ReplaceAll(s, ":", "_")
301
301
+
normalized = strings.ReplaceAll(normalized, ".", "_")
302
302
+
return normalized
302
303
},
303
304
"sshFingerprint": func(pubKey string) string {
304
305
fp, err := crypto.SSHFingerprint(pubKey)
+1
appview/pages/pages.go
···
221
221
222
222
type LoginParams struct {
223
223
ReturnUrl string
224
224
+
ErrorCode string
224
225
}
225
226
226
227
func (p *Pages) Login(w io.Writer, params LoginParams) error {
+2
-2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
···
34
34
35
35
{{ define "editIssueComment" }}
36
36
<a
37
37
-
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group"
37
37
+
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer"
38
38
hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/edit"
39
39
hx-swap="outerHTML"
40
40
hx-target="#comment-body-{{.Comment.Id}}">
···
44
44
45
45
{{ define "deleteIssueComment" }}
46
46
<a
47
47
-
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group"
47
47
+
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer"
48
48
hx-delete="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/"
49
49
hx-confirm="Are you sure you want to delete your comment?"
50
50
hx-swap="outerHTML"
+2
-2
appview/pages/templates/repo/issues/issue.html
···
84
84
85
85
{{ define "editIssue" }}
86
86
<a
87
87
-
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group"
87
87
+
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer"
88
88
hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/edit"
89
89
hx-swap="innerHTML"
90
90
hx-target="#issue-{{.Issue.IssueId}}">
···
94
94
95
95
{{ define "deleteIssue" }}
96
96
<a
97
97
-
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group"
97
97
+
class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer"
98
98
hx-delete="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/"
99
99
hx-confirm="Are you sure you want to delete your issue?"
100
100
hx-swap="none">
+2
appview/pages/templates/repo/settings/access.html
···
83
83
</label>
84
84
<p class="text-sm text-gray-500 dark:text-gray-400">Collaborators can push to this repository.</p>
85
85
<input
86
86
+
autocapitalize="none"
87
87
+
autocorrect="off"
86
88
type="text"
87
89
id="add-collaborator"
88
90
name="collaborator"
+2
appview/pages/templates/spindles/fragments/addMemberModal.html
···
30
30
</label>
31
31
<p class="text-sm text-gray-500 dark:text-gray-400">Members can register repositories and run workflows on this spindle.</p>
32
32
<input
33
33
+
autocapitalize="none"
34
34
+
autocorrect="off"
33
35
type="text"
34
36
id="member-did-{{ .Id }}"
35
37
name="member"
+1
-1
appview/pages/templates/strings/string.html
···
47
47
</span>
48
48
</section>
49
49
<section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full dark:text-white">
50
50
-
<div class="flex justify-between items-center text-gray-500 dark:text-gray-400 text-sm md:text-base pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
50
50
+
<div class="flex flex-col md:flex-row md:justify-between md:items-center text-gray-500 dark:text-gray-400 text-sm md:text-base pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
51
51
<span>
52
52
{{ .String.Filename }}
53
53
<span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span>
+1
-1
appview/pages/templates/user/fragments/followCard.html
···
3
3
<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 rounded-sm">
4
4
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800 flex items-center gap-4">
5
5
<div class="flex-shrink-0 max-h-full w-24 h-24">
6
6
-
<img class="object-cover rounded-full p-2" src="{{ fullAvatar $userIdent }}" />
6
6
+
<img class="object-cover rounded-full p-2" src="{{ fullAvatar $userIdent }}" alt="{{ $userIdent }}" />
7
7
</div>
8
8
9
9
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 w-full">
+23
-2
appview/pages/templates/user/login.html
···
13
13
<title>login · tangled</title>
14
14
</head>
15
15
<body class="flex items-center justify-center min-h-screen">
16
16
-
<main class="max-w-md px-6 -mt-4">
16
16
+
<main class="max-w-md px-7 mt-4">
17
17
<h1 class="flex place-content-center text-3xl font-semibold italic dark:text-white" >
18
18
{{ template "fragments/logotype" }}
19
19
</h1>
···
21
21
tightly-knit social coding.
22
22
</h2>
23
23
<form
24
24
-
class="mt-4 max-w-sm mx-auto"
24
24
+
class="mt-4"
25
25
hx-post="/login"
26
26
hx-swap="none"
27
27
hx-disabled-elt="#login-button"
···
29
29
<div class="flex flex-col">
30
30
<label for="handle">handle</label>
31
31
<input
32
32
+
autocapitalize="none"
33
33
+
autocorrect="off"
34
34
+
autocomplete="username"
32
35
type="text"
33
36
id="handle"
34
37
name="handle"
···
53
56
<span>login</span>
54
57
</button>
55
58
</form>
59
59
+
{{ if .ErrorCode }}
60
60
+
<div class="flex gap-2 my-2 bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-3 py-2 text-red-500 dark:text-red-300">
61
61
+
<span class="py-1">{{ i "circle-alert" "w-4 h-4" }}</span>
62
62
+
<div>
63
63
+
<h5 class="font-medium">Login error</h5>
64
64
+
<p class="text-sm">
65
65
+
{{ if eq .ErrorCode "access_denied" }}
66
66
+
You have not authorized the app.
67
67
+
{{ else if eq .ErrorCode "session" }}
68
68
+
Server failed to create user session.
69
69
+
{{ else }}
70
70
+
Internal Server error.
71
71
+
{{ end }}
72
72
+
Please try again.
73
73
+
</p>
74
74
+
</div>
75
75
+
</div>
76
76
+
{{ end }}
56
77
<p class="text-sm text-gray-500">
57
78
Don't have an account? <a href="/signup" class="underline">Create an account</a> on Tangled now!
58
79
</p>
+23
appview/pagination/page.go
···
1
1
package pagination
2
2
3
3
+
import "context"
4
4
+
3
5
type Page struct {
4
6
Offset int // where to start from
5
7
Limit int // number of items in a page
···
10
12
Offset: 0,
11
13
Limit: 30,
12
14
}
15
15
+
}
16
16
+
17
17
+
type ctxKey struct{}
18
18
+
19
19
+
func IntoContext(ctx context.Context, page Page) context.Context {
20
20
+
return context.WithValue(ctx, ctxKey{}, page)
21
21
+
}
22
22
+
23
23
+
func FromContext(ctx context.Context) Page {
24
24
+
if ctx == nil {
25
25
+
return FirstPage()
26
26
+
}
27
27
+
v := ctx.Value(ctxKey{})
28
28
+
if v == nil {
29
29
+
return FirstPage()
30
30
+
}
31
31
+
page, ok := v.(Page)
32
32
+
if !ok {
33
33
+
return FirstPage()
34
34
+
}
35
35
+
return page
13
36
}
14
37
15
38
func (p Page) Previous() Page {
+1
appview/state/follow.go
···
26
26
subjectIdent, err := s.idResolver.ResolveIdent(r.Context(), subject)
27
27
if err != nil {
28
28
log.Println("failed to follow, invalid did")
29
29
+
return
29
30
}
30
31
31
32
if currentUser.Did == subjectIdent.DID.String() {
+1
-4
appview/state/gfi.go
···
18
18
func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) {
19
19
user := s.oauth.GetUser(r)
20
20
21
21
-
page, ok := r.Context().Value("page").(pagination.Page)
22
22
-
if !ok {
23
23
-
page = pagination.FirstPage()
24
24
-
}
21
21
+
page := pagination.FromContext(r.Context())
25
22
26
23
goodFirstIssueLabel := fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue")
27
24
+2
appview/state/login.go
···
14
14
switch r.Method {
15
15
case http.MethodGet:
16
16
returnURL := r.URL.Query().Get("return_url")
17
17
+
errorCode := r.URL.Query().Get("error")
17
18
s.pages.Login(w, pages.LoginParams{
18
19
ReturnUrl: returnURL,
20
20
+
ErrorCode: errorCode,
19
21
})
20
22
case http.MethodPost:
21
23
handle := r.FormValue("handle")
+1
-1
spindle/engines/nixery/engine.go
···
222
222
},
223
223
ReadonlyRootfs: false,
224
224
CapDrop: []string{"ALL"},
225
225
-
CapAdd: []string{"CAP_DAC_OVERRIDE"},
225
225
+
CapAdd: []string{"CAP_DAC_OVERRIDE", "CAP_CHOWN", "CAP_FOWNER", "CAP_SETUID", "CAP_SETGID"},
226
226
SecurityOpt: []string{"no-new-privileges"},
227
227
ExtraHosts: []string{"host.docker.internal:host-gateway"},
228
228
}, nil, nil, "")