+1
-1
.air/appview.toml
+1
-1
.air/appview.toml
+37
-10
appview/config.go
+37
-10
appview/config.go
···
6
"github.com/sethvargo/go-envconfig"
7
)
8
9
type Config struct {
10
-
CookieSecret string `env:"TANGLED_COOKIE_SECRET, default=00000000000000000000000000000000"`
11
-
DbPath string `env:"TANGLED_DB_PATH, default=appview.db"`
12
-
ListenAddr string `env:"TANGLED_LISTEN_ADDR, default=0.0.0.0:3000"`
13
-
Dev bool `env:"TANGLED_DEV, default=false"`
14
-
JetstreamEndpoint string `env:"TANGLED_JETSTREAM_ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"`
15
-
ResendApiKey string `env:"TANGLED_RESEND_API_KEY"`
16
-
CamoHost string `env:"TANGLED_CAMO_HOST, default=https://camo.tangled.sh"`
17
-
CamoSharedSecret string `env:"TANGLED_CAMO_SHARED_SECRET"`
18
-
AvatarSharedSecret string `env:"TANGLED_AVATAR_SHARED_SECRET"`
19
-
AvatarHost string `env:"TANGLED_AVATAR_HOST, default=https://avatar.tangled.sh"`
20
}
21
22
func LoadConfig(ctx context.Context) (*Config, error) {
···
6
"github.com/sethvargo/go-envconfig"
7
)
8
9
+
type CoreConfig struct {
10
+
CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"`
11
+
DbPath string `env:"DB_PATH, default=appview.db"`
12
+
ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"`
13
+
AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.sh"`
14
+
Dev bool `env:"DEV, default=false"`
15
+
}
16
+
17
+
type OAuthConfig struct {
18
+
Jwks string `env:"JWKS"`
19
+
ServerMetadataUrl string `env:"SERVER_METADATA_URL"`
20
+
}
21
+
22
+
type JetstreamConfig struct {
23
+
Endpoint string `env:"ENDPOINT, default=wss://jetstream1.us-east.bsky.network/subscribe"`
24
+
}
25
+
26
+
type ResendConfig struct {
27
+
ApiKey string `env:"API_KEY"`
28
+
}
29
+
30
+
type CamoConfig struct {
31
+
Host string `env:"HOST, default=https://camo.tangled.sh"`
32
+
SharedSecret string `env:"SHARED_SECRET"`
33
+
}
34
+
35
+
type AvatarConfig struct {
36
+
Host string `env:"HOST, default=https://avatar.tangled.sh"`
37
+
SharedSecret string `env:"SHARED_SECRET"`
38
+
}
39
+
40
type Config struct {
41
+
Core CoreConfig `env:",prefix=TANGLED_"`
42
+
Jetstream JetstreamConfig `env:",prefix=TANGLED_JETSTREAM_"`
43
+
Resend ResendConfig `env:",prefix=TANGLED_RESEND_"`
44
+
Camo CamoConfig `env:",prefix=TANGLED_CAMO_"`
45
+
Avatar AvatarConfig `env:",prefix=TANGLED_AVATAR_"`
46
+
OAuth OAuthConfig `env:",prefix=TANGLED_OAUTH_"`
47
}
48
49
func LoadConfig(ctx context.Context) (*Config, error) {
+3
appview/consts.go
+3
appview/consts.go
+26
appview/db/db.go
+26
appview/db/db.go
···
288
foreign key (at_uri) references repos(at_uri) on delete cascade
289
);
290
291
+
create table if not exists oauth_requests (
292
+
id integer primary key autoincrement,
293
+
auth_server_iss text not null,
294
+
state text not null,
295
+
did text not null,
296
+
handle text not null,
297
+
pds_url text not null,
298
+
pkce_verifier text not null,
299
+
dpop_auth_server_nonce text not null,
300
+
dpop_private_jwk text not null
301
+
);
302
+
303
+
create table if not exists oauth_sessions (
304
+
id integer primary key autoincrement,
305
+
did text not null,
306
+
handle text not null,
307
+
pds_url text not null,
308
+
auth_server_iss text not null,
309
+
access_jwt text not null,
310
+
refresh_jwt text not null,
311
+
dpop_pds_nonce text,
312
+
dpop_auth_server_nonce text not null,
313
+
dpop_private_jwk text not null,
314
+
expiry text not null
315
+
);
316
+
317
create table if not exists migrations (
318
id integer primary key autoincrement,
319
name text unique
+173
appview/db/oauth.go
+173
appview/db/oauth.go
···
···
1
+
package db
2
+
3
+
type OAuthRequest struct {
4
+
ID uint
5
+
AuthserverIss string
6
+
Handle string
7
+
State string
8
+
Did string
9
+
PdsUrl string
10
+
PkceVerifier string
11
+
DpopAuthserverNonce string
12
+
DpopPrivateJwk string
13
+
}
14
+
15
+
func SaveOAuthRequest(e Execer, oauthRequest OAuthRequest) error {
16
+
_, err := e.Exec(`
17
+
insert into oauth_requests (
18
+
auth_server_iss,
19
+
state,
20
+
handle,
21
+
did,
22
+
pds_url,
23
+
pkce_verifier,
24
+
dpop_auth_server_nonce,
25
+
dpop_private_jwk
26
+
) values (?, ?, ?, ?, ?, ?, ?, ?)`,
27
+
oauthRequest.AuthserverIss,
28
+
oauthRequest.State,
29
+
oauthRequest.Handle,
30
+
oauthRequest.Did,
31
+
oauthRequest.PdsUrl,
32
+
oauthRequest.PkceVerifier,
33
+
oauthRequest.DpopAuthserverNonce,
34
+
oauthRequest.DpopPrivateJwk,
35
+
)
36
+
return err
37
+
}
38
+
39
+
func GetOAuthRequestByState(e Execer, state string) (OAuthRequest, error) {
40
+
var req OAuthRequest
41
+
err := e.QueryRow(`
42
+
select
43
+
id,
44
+
auth_server_iss,
45
+
handle,
46
+
state,
47
+
did,
48
+
pds_url,
49
+
pkce_verifier,
50
+
dpop_auth_server_nonce,
51
+
dpop_private_jwk
52
+
from oauth_requests
53
+
where state = ?`, state).Scan(
54
+
&req.ID,
55
+
&req.AuthserverIss,
56
+
&req.Handle,
57
+
&req.State,
58
+
&req.Did,
59
+
&req.PdsUrl,
60
+
&req.PkceVerifier,
61
+
&req.DpopAuthserverNonce,
62
+
&req.DpopPrivateJwk,
63
+
)
64
+
return req, err
65
+
}
66
+
67
+
func DeleteOAuthRequestByState(e Execer, state string) error {
68
+
_, err := e.Exec(`
69
+
delete from oauth_requests
70
+
where state = ?`, state)
71
+
return err
72
+
}
73
+
74
+
type OAuthSession struct {
75
+
ID uint
76
+
Handle string
77
+
Did string
78
+
PdsUrl string
79
+
AccessJwt string
80
+
RefreshJwt string
81
+
AuthServerIss string
82
+
DpopPdsNonce string
83
+
DpopAuthserverNonce string
84
+
DpopPrivateJwk string
85
+
Expiry string
86
+
}
87
+
88
+
func SaveOAuthSession(e Execer, session OAuthSession) error {
89
+
_, err := e.Exec(`
90
+
insert into oauth_sessions (
91
+
did,
92
+
handle,
93
+
pds_url,
94
+
access_jwt,
95
+
refresh_jwt,
96
+
auth_server_iss,
97
+
dpop_auth_server_nonce,
98
+
dpop_private_jwk,
99
+
expiry
100
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
101
+
session.Did,
102
+
session.Handle,
103
+
session.PdsUrl,
104
+
session.AccessJwt,
105
+
session.RefreshJwt,
106
+
session.AuthServerIss,
107
+
session.DpopAuthserverNonce,
108
+
session.DpopPrivateJwk,
109
+
session.Expiry,
110
+
)
111
+
return err
112
+
}
113
+
114
+
func RefreshOAuthSession(e Execer, did string, accessJwt, refreshJwt, expiry string) error {
115
+
_, err := e.Exec(`
116
+
update oauth_sessions
117
+
set access_jwt = ?, refresh_jwt = ?, expiry = ?
118
+
where did = ?`,
119
+
accessJwt,
120
+
refreshJwt,
121
+
expiry,
122
+
did,
123
+
)
124
+
return err
125
+
}
126
+
127
+
func GetOAuthSessionByDid(e Execer, did string) (*OAuthSession, error) {
128
+
var session OAuthSession
129
+
err := e.QueryRow(`
130
+
select
131
+
id,
132
+
did,
133
+
handle,
134
+
pds_url,
135
+
access_jwt,
136
+
refresh_jwt,
137
+
auth_server_iss,
138
+
dpop_auth_server_nonce,
139
+
dpop_private_jwk,
140
+
expiry
141
+
from oauth_sessions
142
+
where did = ?`, did).Scan(
143
+
&session.ID,
144
+
&session.Did,
145
+
&session.Handle,
146
+
&session.PdsUrl,
147
+
&session.AccessJwt,
148
+
&session.RefreshJwt,
149
+
&session.AuthServerIss,
150
+
&session.DpopAuthserverNonce,
151
+
&session.DpopPrivateJwk,
152
+
&session.Expiry,
153
+
)
154
+
return &session, err
155
+
}
156
+
157
+
func DeleteOAuthSessionByDid(e Execer, did string) error {
158
+
_, err := e.Exec(`
159
+
delete from oauth_sessions
160
+
where did = ?`, did)
161
+
return err
162
+
}
163
+
164
+
func UpdateDpopPdsNonce(e Execer, did string, dpopPdsNonce string) error {
165
+
_, err := e.Exec(`
166
+
update oauth_sessions
167
+
set dpop_pds_nonce = ?
168
+
where did = ?`,
169
+
dpopPdsNonce,
170
+
did,
171
+
)
172
+
return err
173
+
}
+5
-58
appview/middleware/middleware.go
+5
-58
appview/middleware/middleware.go
···
5
"log"
6
"net/http"
7
"strconv"
8
-
"time"
9
10
-
comatproto "github.com/bluesky-social/indigo/api/atproto"
11
-
"github.com/bluesky-social/indigo/xrpc"
12
-
"tangled.sh/tangled.sh/core/appview"
13
-
"tangled.sh/tangled.sh/core/appview/auth"
14
"tangled.sh/tangled.sh/core/appview/pagination"
15
)
16
17
type Middleware func(http.Handler) http.Handler
18
19
-
func AuthMiddleware(a *auth.Auth) Middleware {
20
return func(next http.Handler) http.Handler {
21
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
redirectFunc := func(w http.ResponseWriter, r *http.Request) {
···
29
}
30
}
31
32
-
session, err := a.GetSession(r)
33
-
if session.IsNew || err != nil {
34
log.Printf("not logged in, redirecting")
35
redirectFunc(w, r)
36
return
37
}
38
39
-
authorized, ok := session.Values[appview.SessionAuthenticated].(bool)
40
-
if !ok || !authorized {
41
log.Printf("not logged in, redirecting")
42
redirectFunc(w, r)
43
return
44
-
}
45
-
46
-
// refresh if nearing expiry
47
-
// TODO: dedup with /login
48
-
expiryStr := session.Values[appview.SessionExpiry].(string)
49
-
expiry, err := time.Parse(time.RFC3339, expiryStr)
50
-
if err != nil {
51
-
log.Println("invalid expiry time", err)
52
-
redirectFunc(w, r)
53
-
return
54
-
}
55
-
pdsUrl, ok1 := session.Values[appview.SessionPds].(string)
56
-
did, ok2 := session.Values[appview.SessionDid].(string)
57
-
refreshJwt, ok3 := session.Values[appview.SessionRefreshJwt].(string)
58
-
59
-
if !ok1 || !ok2 || !ok3 {
60
-
log.Println("invalid expiry time", err)
61
-
redirectFunc(w, r)
62
-
return
63
-
}
64
-
65
-
if time.Now().After(expiry) {
66
-
log.Println("token expired, refreshing ...")
67
-
68
-
client := xrpc.Client{
69
-
Host: pdsUrl,
70
-
Auth: &xrpc.AuthInfo{
71
-
Did: did,
72
-
AccessJwt: refreshJwt,
73
-
RefreshJwt: refreshJwt,
74
-
},
75
-
}
76
-
atSession, err := comatproto.ServerRefreshSession(r.Context(), &client)
77
-
if err != nil {
78
-
log.Println("failed to refresh session", err)
79
-
redirectFunc(w, r)
80
-
return
81
-
}
82
-
83
-
sessionish := auth.RefreshSessionWrapper{atSession}
84
-
85
-
err = a.StoreSession(r, w, &sessionish, pdsUrl)
86
-
if err != nil {
87
-
log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err)
88
-
return
89
-
}
90
-
91
-
log.Println("successfully refreshed token")
92
}
93
94
next.ServeHTTP(w, r)
···
5
"log"
6
"net/http"
7
"strconv"
8
9
+
"tangled.sh/tangled.sh/core/appview/oauth"
10
"tangled.sh/tangled.sh/core/appview/pagination"
11
)
12
13
type Middleware func(http.Handler) http.Handler
14
15
+
func AuthMiddleware(a *oauth.OAuth) Middleware {
16
return func(next http.Handler) http.Handler {
17
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18
redirectFunc := func(w http.ResponseWriter, r *http.Request) {
···
25
}
26
}
27
28
+
_, auth, err := a.GetSession(r)
29
+
if err != nil {
30
log.Printf("not logged in, redirecting")
31
redirectFunc(w, r)
32
return
33
}
34
35
+
if !auth {
36
log.Printf("not logged in, redirecting")
37
redirectFunc(w, r)
38
return
39
}
40
41
next.ServeHTTP(w, r)
+2
appview/oauth/oauth.go
+2
appview/oauth/oauth.go
+41
-37
appview/pages/pages.go
+41
-37
appview/pages/pages.go
···
16
"strings"
17
18
"tangled.sh/tangled.sh/core/appview"
19
-
"tangled.sh/tangled.sh/core/appview/auth"
20
"tangled.sh/tangled.sh/core/appview/db"
21
"tangled.sh/tangled.sh/core/appview/pages/markup"
22
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
23
"tangled.sh/tangled.sh/core/appview/pagination"
···
48
func NewPages(config *appview.Config) *Pages {
49
// initialized with safe defaults, can be overriden per use
50
rctx := &markup.RenderContext{
51
-
IsDev: config.Dev,
52
-
CamoUrl: config.CamoHost,
53
-
CamoSecret: config.CamoSharedSecret,
54
}
55
56
p := &Pages{
57
t: make(map[string]*template.Template),
58
-
dev: config.Dev,
59
embedFS: Files,
60
rctx: rctx,
61
templateDir: "appview/pages",
···
249
return p.executePlain("user/login", w, params)
250
}
251
252
type TimelineParams struct {
253
-
LoggedInUser *auth.User
254
Timeline []db.TimelineEvent
255
DidHandleMap map[string]string
256
}
···
260
}
261
262
type SettingsParams struct {
263
-
LoggedInUser *auth.User
264
PubKeys []db.PublicKey
265
Emails []db.Email
266
}
···
270
}
271
272
type KnotsParams struct {
273
-
LoggedInUser *auth.User
274
Registrations []db.Registration
275
}
276
···
279
}
280
281
type KnotParams struct {
282
-
LoggedInUser *auth.User
283
DidHandleMap map[string]string
284
Registration *db.Registration
285
Members []string
···
291
}
292
293
type NewRepoParams struct {
294
-
LoggedInUser *auth.User
295
Knots []string
296
}
297
···
300
}
301
302
type ForkRepoParams struct {
303
-
LoggedInUser *auth.User
304
Knots []string
305
RepoInfo repoinfo.RepoInfo
306
}
···
310
}
311
312
type ProfilePageParams struct {
313
-
LoggedInUser *auth.User
314
Repos []db.Repo
315
CollaboratingRepos []db.Repo
316
ProfileTimeline *db.ProfileTimeline
···
335
}
336
337
type ReposPageParams struct {
338
-
LoggedInUser *auth.User
339
Repos []db.Repo
340
Card ProfileCard
341
···
356
}
357
358
type EditBioParams struct {
359
-
LoggedInUser *auth.User
360
Profile *db.Profile
361
}
362
···
365
}
366
367
type EditPinsParams struct {
368
-
LoggedInUser *auth.User
369
Profile *db.Profile
370
AllRepos []PinnedRepo
371
DidHandleMap map[string]string
···
403
}
404
405
type RepoIndexParams struct {
406
-
LoggedInUser *auth.User
407
RepoInfo repoinfo.RepoInfo
408
Active string
409
TagMap map[string][]string
···
444
}
445
446
type RepoLogParams struct {
447
-
LoggedInUser *auth.User
448
RepoInfo repoinfo.RepoInfo
449
TagMap map[string][]string
450
types.RepoLogResponse
···
458
}
459
460
type RepoCommitParams struct {
461
-
LoggedInUser *auth.User
462
RepoInfo repoinfo.RepoInfo
463
Active string
464
EmailToDidOrHandle map[string]string
···
472
}
473
474
type RepoTreeParams struct {
475
-
LoggedInUser *auth.User
476
RepoInfo repoinfo.RepoInfo
477
Active string
478
BreadCrumbs [][]string
···
508
}
509
510
type RepoBranchesParams struct {
511
-
LoggedInUser *auth.User
512
RepoInfo repoinfo.RepoInfo
513
Active string
514
types.RepoBranchesResponse
···
520
}
521
522
type RepoTagsParams struct {
523
-
LoggedInUser *auth.User
524
RepoInfo repoinfo.RepoInfo
525
Active string
526
types.RepoTagsResponse
···
534
}
535
536
type RepoArtifactParams struct {
537
-
LoggedInUser *auth.User
538
RepoInfo repoinfo.RepoInfo
539
Artifact db.Artifact
540
}
···
544
}
545
546
type RepoBlobParams struct {
547
-
LoggedInUser *auth.User
548
RepoInfo repoinfo.RepoInfo
549
Active string
550
BreadCrumbs [][]string
···
606
}
607
608
type RepoSettingsParams struct {
609
-
LoggedInUser *auth.User
610
RepoInfo repoinfo.RepoInfo
611
Collaborators []Collaborator
612
Active string
···
622
}
623
624
type RepoIssuesParams struct {
625
-
LoggedInUser *auth.User
626
RepoInfo repoinfo.RepoInfo
627
Active string
628
Issues []db.Issue
···
637
}
638
639
type RepoSingleIssueParams struct {
640
-
LoggedInUser *auth.User
641
RepoInfo repoinfo.RepoInfo
642
Active string
643
Issue db.Issue
···
659
}
660
661
type RepoNewIssueParams struct {
662
-
LoggedInUser *auth.User
663
RepoInfo repoinfo.RepoInfo
664
Active string
665
}
···
670
}
671
672
type EditIssueCommentParams struct {
673
-
LoggedInUser *auth.User
674
RepoInfo repoinfo.RepoInfo
675
Issue *db.Issue
676
Comment *db.Comment
···
681
}
682
683
type SingleIssueCommentParams struct {
684
-
LoggedInUser *auth.User
685
DidHandleMap map[string]string
686
RepoInfo repoinfo.RepoInfo
687
Issue *db.Issue
···
693
}
694
695
type RepoNewPullParams struct {
696
-
LoggedInUser *auth.User
697
RepoInfo repoinfo.RepoInfo
698
Branches []types.Branch
699
Active string
···
705
}
706
707
type RepoPullsParams struct {
708
-
LoggedInUser *auth.User
709
RepoInfo repoinfo.RepoInfo
710
Pulls []*db.Pull
711
Active string
···
737
}
738
739
type RepoSinglePullParams struct {
740
-
LoggedInUser *auth.User
741
RepoInfo repoinfo.RepoInfo
742
Active string
743
DidHandleMap map[string]string
···
752
}
753
754
type RepoPullPatchParams struct {
755
-
LoggedInUser *auth.User
756
DidHandleMap map[string]string
757
RepoInfo repoinfo.RepoInfo
758
Pull *db.Pull
···
767
}
768
769
type RepoPullInterdiffParams struct {
770
-
LoggedInUser *auth.User
771
DidHandleMap map[string]string
772
RepoInfo repoinfo.RepoInfo
773
Pull *db.Pull
···
817
}
818
819
type PullResubmitParams struct {
820
-
LoggedInUser *auth.User
821
RepoInfo repoinfo.RepoInfo
822
Pull *db.Pull
823
SubmissionId int
···
828
}
829
830
type PullActionsParams struct {
831
-
LoggedInUser *auth.User
832
RepoInfo repoinfo.RepoInfo
833
Pull *db.Pull
834
RoundNumber int
···
841
}
842
843
type PullNewCommentParams struct {
844
-
LoggedInUser *auth.User
845
RepoInfo repoinfo.RepoInfo
846
Pull *db.Pull
847
RoundNumber int
···
16
"strings"
17
18
"tangled.sh/tangled.sh/core/appview"
19
"tangled.sh/tangled.sh/core/appview/db"
20
+
"tangled.sh/tangled.sh/core/appview/oauth"
21
"tangled.sh/tangled.sh/core/appview/pages/markup"
22
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
23
"tangled.sh/tangled.sh/core/appview/pagination"
···
48
func NewPages(config *appview.Config) *Pages {
49
// initialized with safe defaults, can be overriden per use
50
rctx := &markup.RenderContext{
51
+
IsDev: config.Core.Dev,
52
+
CamoUrl: config.Camo.Host,
53
+
CamoSecret: config.Camo.SharedSecret,
54
}
55
56
p := &Pages{
57
t: make(map[string]*template.Template),
58
+
dev: config.Core.Dev,
59
embedFS: Files,
60
rctx: rctx,
61
templateDir: "appview/pages",
···
249
return p.executePlain("user/login", w, params)
250
}
251
252
+
func (p *Pages) OAuthLogin(w io.Writer, params LoginParams) error {
253
+
return p.executePlain("user/oauthlogin", w, params)
254
+
}
255
+
256
type TimelineParams struct {
257
+
LoggedInUser *oauth.User
258
Timeline []db.TimelineEvent
259
DidHandleMap map[string]string
260
}
···
264
}
265
266
type SettingsParams struct {
267
+
LoggedInUser *oauth.User
268
PubKeys []db.PublicKey
269
Emails []db.Email
270
}
···
274
}
275
276
type KnotsParams struct {
277
+
LoggedInUser *oauth.User
278
Registrations []db.Registration
279
}
280
···
283
}
284
285
type KnotParams struct {
286
+
LoggedInUser *oauth.User
287
DidHandleMap map[string]string
288
Registration *db.Registration
289
Members []string
···
295
}
296
297
type NewRepoParams struct {
298
+
LoggedInUser *oauth.User
299
Knots []string
300
}
301
···
304
}
305
306
type ForkRepoParams struct {
307
+
LoggedInUser *oauth.User
308
Knots []string
309
RepoInfo repoinfo.RepoInfo
310
}
···
314
}
315
316
type ProfilePageParams struct {
317
+
LoggedInUser *oauth.User
318
Repos []db.Repo
319
CollaboratingRepos []db.Repo
320
ProfileTimeline *db.ProfileTimeline
···
339
}
340
341
type ReposPageParams struct {
342
+
LoggedInUser *oauth.User
343
Repos []db.Repo
344
Card ProfileCard
345
···
360
}
361
362
type EditBioParams struct {
363
+
LoggedInUser *oauth.User
364
Profile *db.Profile
365
}
366
···
369
}
370
371
type EditPinsParams struct {
372
+
LoggedInUser *oauth.User
373
Profile *db.Profile
374
AllRepos []PinnedRepo
375
DidHandleMap map[string]string
···
407
}
408
409
type RepoIndexParams struct {
410
+
LoggedInUser *oauth.User
411
RepoInfo repoinfo.RepoInfo
412
Active string
413
TagMap map[string][]string
···
448
}
449
450
type RepoLogParams struct {
451
+
LoggedInUser *oauth.User
452
RepoInfo repoinfo.RepoInfo
453
TagMap map[string][]string
454
types.RepoLogResponse
···
462
}
463
464
type RepoCommitParams struct {
465
+
LoggedInUser *oauth.User
466
RepoInfo repoinfo.RepoInfo
467
Active string
468
EmailToDidOrHandle map[string]string
···
476
}
477
478
type RepoTreeParams struct {
479
+
LoggedInUser *oauth.User
480
RepoInfo repoinfo.RepoInfo
481
Active string
482
BreadCrumbs [][]string
···
512
}
513
514
type RepoBranchesParams struct {
515
+
LoggedInUser *oauth.User
516
RepoInfo repoinfo.RepoInfo
517
Active string
518
types.RepoBranchesResponse
···
524
}
525
526
type RepoTagsParams struct {
527
+
LoggedInUser *oauth.User
528
RepoInfo repoinfo.RepoInfo
529
Active string
530
types.RepoTagsResponse
···
538
}
539
540
type RepoArtifactParams struct {
541
+
LoggedInUser *oauth.User
542
RepoInfo repoinfo.RepoInfo
543
Artifact db.Artifact
544
}
···
548
}
549
550
type RepoBlobParams struct {
551
+
LoggedInUser *oauth.User
552
RepoInfo repoinfo.RepoInfo
553
Active string
554
BreadCrumbs [][]string
···
610
}
611
612
type RepoSettingsParams struct {
613
+
LoggedInUser *oauth.User
614
RepoInfo repoinfo.RepoInfo
615
Collaborators []Collaborator
616
Active string
···
626
}
627
628
type RepoIssuesParams struct {
629
+
LoggedInUser *oauth.User
630
RepoInfo repoinfo.RepoInfo
631
Active string
632
Issues []db.Issue
···
641
}
642
643
type RepoSingleIssueParams struct {
644
+
LoggedInUser *oauth.User
645
RepoInfo repoinfo.RepoInfo
646
Active string
647
Issue db.Issue
···
663
}
664
665
type RepoNewIssueParams struct {
666
+
LoggedInUser *oauth.User
667
RepoInfo repoinfo.RepoInfo
668
Active string
669
}
···
674
}
675
676
type EditIssueCommentParams struct {
677
+
LoggedInUser *oauth.User
678
RepoInfo repoinfo.RepoInfo
679
Issue *db.Issue
680
Comment *db.Comment
···
685
}
686
687
type SingleIssueCommentParams struct {
688
+
LoggedInUser *oauth.User
689
DidHandleMap map[string]string
690
RepoInfo repoinfo.RepoInfo
691
Issue *db.Issue
···
697
}
698
699
type RepoNewPullParams struct {
700
+
LoggedInUser *oauth.User
701
RepoInfo repoinfo.RepoInfo
702
Branches []types.Branch
703
Active string
···
709
}
710
711
type RepoPullsParams struct {
712
+
LoggedInUser *oauth.User
713
RepoInfo repoinfo.RepoInfo
714
Pulls []*db.Pull
715
Active string
···
741
}
742
743
type RepoSinglePullParams struct {
744
+
LoggedInUser *oauth.User
745
RepoInfo repoinfo.RepoInfo
746
Active string
747
DidHandleMap map[string]string
···
756
}
757
758
type RepoPullPatchParams struct {
759
+
LoggedInUser *oauth.User
760
DidHandleMap map[string]string
761
RepoInfo repoinfo.RepoInfo
762
Pull *db.Pull
···
771
}
772
773
type RepoPullInterdiffParams struct {
774
+
LoggedInUser *oauth.User
775
DidHandleMap map[string]string
776
RepoInfo repoinfo.RepoInfo
777
Pull *db.Pull
···
821
}
822
823
type PullResubmitParams struct {
824
+
LoggedInUser *oauth.User
825
RepoInfo repoinfo.RepoInfo
826
Pull *db.Pull
827
SubmissionId int
···
832
}
833
834
type PullActionsParams struct {
835
+
LoggedInUser *oauth.User
836
RepoInfo repoinfo.RepoInfo
837
Pull *db.Pull
838
RoundNumber int
···
845
}
846
847
type PullNewCommentParams struct {
848
+
LoggedInUser *oauth.User
849
RepoInfo repoinfo.RepoInfo
850
Pull *db.Pull
851
RoundNumber int
+71
appview/pages/templates/user/oauthlogin.html
+71
appview/pages/templates/user/oauthlogin.html
···
···
1
+
{{ define "user/oauthlogin" }}
2
+
<!doctype html>
3
+
<html lang="en" class="dark:bg-gray-900">
4
+
<head>
5
+
<meta charset="UTF-8" />
6
+
<meta
7
+
name="viewport"
8
+
content="width=device-width, initial-scale=1.0"
9
+
/>
10
+
<script src="/static/htmx.min.js"></script>
11
+
<link
12
+
rel="stylesheet"
13
+
href="/static/tw.css?{{ cssContentHash }}"
14
+
type="text/css"
15
+
/>
16
+
<title>login</title>
17
+
</head>
18
+
<body class="flex items-center justify-center min-h-screen">
19
+
<main class="max-w-7xl px-6 -mt-4">
20
+
<h1
21
+
class="text-center text-2xl font-semibold italic dark:text-white"
22
+
>
23
+
tangled
24
+
</h1>
25
+
<h2 class="text-center text-xl italic dark:text-white">
26
+
tightly-knit social coding.
27
+
</h2>
28
+
<form
29
+
class="w-full mt-4"
30
+
hx-post="/oauth/login"
31
+
hx-swap="none"
32
+
hx-disabled-elt="this"
33
+
>
34
+
<div class="flex flex-col">
35
+
<label for="handle">handle</label>
36
+
<input
37
+
type="text"
38
+
id="handle"
39
+
name="handle"
40
+
tabindex="1"
41
+
required
42
+
/>
43
+
<span class="text-xs text-gray-500 mt-1">
44
+
Use your
45
+
<a href="https://bsky.app">Bluesky</a> handle to log
46
+
in. You will then be redirected to your PDS to
47
+
complete authentication.
48
+
</span>
49
+
</div>
50
+
51
+
<button
52
+
class="btn w-full my-2 mt-6"
53
+
type="submit"
54
+
id="login-button"
55
+
tabindex="3"
56
+
>
57
+
<span>login</span>
58
+
</button>
59
+
</form>
60
+
<p class="text-sm text-gray-500">
61
+
Join our <a href="https://chat.tangled.sh">Discord</a> or
62
+
IRC channel:
63
+
<a href="https://web.libera.chat/#tangled"
64
+
><code>#tangled</code> on Libera Chat</a
65
+
>.
66
+
</p>
67
+
<p id="login-msg" class="error w-full"></p>
68
+
</main>
69
+
</body>
70
+
</html>
71
+
{{ end }}
+27
-18
appview/settings/settings.go
+27
-18
appview/settings/settings.go
···
13
"github.com/go-chi/chi/v5"
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview"
16
-
"tangled.sh/tangled.sh/core/appview/auth"
17
"tangled.sh/tangled.sh/core/appview/db"
18
"tangled.sh/tangled.sh/core/appview/email"
19
"tangled.sh/tangled.sh/core/appview/middleware"
20
"tangled.sh/tangled.sh/core/appview/pages"
21
22
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
27
28
type Settings struct {
29
Db *db.DB
30
-
Auth *auth.Auth
31
Pages *pages.Pages
32
Config *appview.Config
33
}
···
35
func (s *Settings) Router() http.Handler {
36
r := chi.NewRouter()
37
38
-
r.Use(middleware.AuthMiddleware(s.Auth))
39
40
r.Get("/", s.settings)
41
···
56
}
57
58
func (s *Settings) settings(w http.ResponseWriter, r *http.Request) {
59
-
user := s.Auth.GetUser(r)
60
pubKeys, err := db.GetPublicKeys(s.Db, user.Did)
61
if err != nil {
62
log.Println(err)
···
79
verifyURL := s.verifyUrl(did, emailAddr, code)
80
81
return email.Email{
82
-
APIKey: s.Config.ResendApiKey,
83
From: "noreply@notifs.tangled.sh",
84
To: emailAddr,
85
Subject: "Verify your Tangled email",
···
111
log.Println("unimplemented")
112
return
113
case http.MethodPut:
114
-
did := s.Auth.GetDid(r)
115
emAddr := r.FormValue("email")
116
emAddr = strings.TrimSpace(emAddr)
117
···
174
s.Pages.Notice(w, "settings-emails-success", "Click the link in the email we sent you to verify your email address.")
175
return
176
case http.MethodDelete:
177
-
did := s.Auth.GetDid(r)
178
emailAddr := r.FormValue("email")
179
emailAddr = strings.TrimSpace(emailAddr)
180
···
207
208
func (s *Settings) verifyUrl(did string, email string, code string) string {
209
var appUrl string
210
-
if s.Config.Dev {
211
-
appUrl = "http://" + s.Config.ListenAddr
212
} else {
213
appUrl = "https://tangled.sh"
214
}
···
252
return
253
}
254
255
-
did := s.Auth.GetDid(r)
256
emAddr := r.FormValue("email")
257
emAddr = strings.TrimSpace(emAddr)
258
···
323
}
324
325
func (s *Settings) emailsPrimary(w http.ResponseWriter, r *http.Request) {
326
-
did := s.Auth.GetDid(r)
327
emailAddr := r.FormValue("email")
328
emailAddr = strings.TrimSpace(emailAddr)
329
···
348
log.Println("unimplemented")
349
return
350
case http.MethodPut:
351
-
did := s.Auth.GetDid(r)
352
key := r.FormValue("key")
353
key = strings.TrimSpace(key)
354
name := r.FormValue("name")
355
-
client, _ := s.Auth.AuthorizedClient(r)
356
357
-
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
358
if err != nil {
359
log.Printf("parsing public key: %s", err)
360
s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.")
···
378
}
379
380
// store in pds too
381
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
382
Collection: tangled.PublicKeyNSID,
383
Repo: did,
384
Rkey: rkey,
···
409
return
410
411
case http.MethodDelete:
412
-
did := s.Auth.GetDid(r)
413
q := r.URL.Query()
414
415
name := q.Get("name")
···
420
log.Println(rkey)
421
log.Println(key)
422
423
-
client, _ := s.Auth.AuthorizedClient(r)
424
425
if err := db.DeletePublicKey(s.Db, did, name, key); err != nil {
426
log.Printf("removing public key: %s", err)
···
430
431
if rkey != "" {
432
// remove from pds too
433
-
_, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
434
Collection: tangled.PublicKeyNSID,
435
Repo: did,
436
Rkey: rkey,
···
13
"github.com/go-chi/chi/v5"
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview"
16
"tangled.sh/tangled.sh/core/appview/db"
17
"tangled.sh/tangled.sh/core/appview/email"
18
"tangled.sh/tangled.sh/core/appview/middleware"
19
+
"tangled.sh/tangled.sh/core/appview/oauth"
20
"tangled.sh/tangled.sh/core/appview/pages"
21
22
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
27
28
type Settings struct {
29
Db *db.DB
30
+
OAuth *oauth.OAuth
31
Pages *pages.Pages
32
Config *appview.Config
33
}
···
35
func (s *Settings) Router() http.Handler {
36
r := chi.NewRouter()
37
38
+
r.Use(middleware.AuthMiddleware(s.OAuth))
39
40
r.Get("/", s.settings)
41
···
56
}
57
58
func (s *Settings) settings(w http.ResponseWriter, r *http.Request) {
59
+
user := s.OAuth.GetUser(r)
60
pubKeys, err := db.GetPublicKeys(s.Db, user.Did)
61
if err != nil {
62
log.Println(err)
···
79
verifyURL := s.verifyUrl(did, emailAddr, code)
80
81
return email.Email{
82
+
APIKey: s.Config.Resend.ApiKey,
83
From: "noreply@notifs.tangled.sh",
84
To: emailAddr,
85
Subject: "Verify your Tangled email",
···
111
log.Println("unimplemented")
112
return
113
case http.MethodPut:
114
+
did := s.OAuth.GetDid(r)
115
emAddr := r.FormValue("email")
116
emAddr = strings.TrimSpace(emAddr)
117
···
174
s.Pages.Notice(w, "settings-emails-success", "Click the link in the email we sent you to verify your email address.")
175
return
176
case http.MethodDelete:
177
+
did := s.OAuth.GetDid(r)
178
emailAddr := r.FormValue("email")
179
emailAddr = strings.TrimSpace(emailAddr)
180
···
207
208
func (s *Settings) verifyUrl(did string, email string, code string) string {
209
var appUrl string
210
+
if s.Config.Core.Dev {
211
+
appUrl = "http://" + s.Config.Core.ListenAddr
212
} else {
213
appUrl = "https://tangled.sh"
214
}
···
252
return
253
}
254
255
+
did := s.OAuth.GetDid(r)
256
emAddr := r.FormValue("email")
257
emAddr = strings.TrimSpace(emAddr)
258
···
323
}
324
325
func (s *Settings) emailsPrimary(w http.ResponseWriter, r *http.Request) {
326
+
did := s.OAuth.GetDid(r)
327
emailAddr := r.FormValue("email")
328
emailAddr = strings.TrimSpace(emailAddr)
329
···
348
log.Println("unimplemented")
349
return
350
case http.MethodPut:
351
+
did := s.OAuth.GetDid(r)
352
key := r.FormValue("key")
353
key = strings.TrimSpace(key)
354
name := r.FormValue("name")
355
+
client, err := s.OAuth.AuthorizedClient(r)
356
+
if err != nil {
357
+
s.Pages.Notice(w, "settings-keys", "Failed to authorize. Try again later.")
358
+
return
359
+
}
360
361
+
_, _, _, _, err = ssh.ParseAuthorizedKey([]byte(key))
362
if err != nil {
363
log.Printf("parsing public key: %s", err)
364
s.Pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.")
···
382
}
383
384
// store in pds too
385
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
386
Collection: tangled.PublicKeyNSID,
387
Repo: did,
388
Rkey: rkey,
···
413
return
414
415
case http.MethodDelete:
416
+
did := s.OAuth.GetDid(r)
417
q := r.URL.Query()
418
419
name := q.Get("name")
···
424
log.Println(rkey)
425
log.Println(key)
426
427
+
client, err := s.OAuth.AuthorizedClient(r)
428
+
if err != nil {
429
+
log.Printf("failed to authorize client: %s", err)
430
+
s.Pages.Notice(w, "settings-keys", "Failed to authorize client.")
431
+
return
432
+
}
433
434
if err := db.DeletePublicKey(s.Db, did, name, key); err != nil {
435
log.Printf("removing public key: %s", err)
···
439
440
if rkey != "" {
441
// remove from pds too
442
+
_, err := client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
443
Collection: tangled.PublicKeyNSID,
444
Repo: did,
445
Rkey: rkey,
+19
-10
appview/state/artifact.go
+19
-10
appview/state/artifact.go
···
22
23
// TODO: proper statuses here on early exit
24
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
25
-
user := s.auth.GetUser(r)
26
tagParam := chi.URLParam(r, "tag")
27
f, err := s.fullyResolvedRepo(r)
28
if err != nil {
···
46
}
47
defer file.Close()
48
49
-
client, _ := s.auth.AuthorizedClient(r)
50
51
-
uploadBlobResp, err := comatproto.RepoUploadBlob(r.Context(), client, file)
52
if err != nil {
53
log.Println("failed to upload blob", err)
54
s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.")
···
60
rkey := appview.TID()
61
createdAt := time.Now()
62
63
-
putRecordResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
64
Collection: tangled.RepoArtifactNSID,
65
Repo: user.Did,
66
Rkey: rkey,
···
140
return
141
}
142
143
-
client, _ := s.auth.AuthorizedClient(r)
144
145
artifacts, err := db.GetArtifact(
146
s.db,
···
159
160
artifact := artifacts[0]
161
162
-
getBlobResp, err := comatproto.SyncGetBlob(r.Context(), client, artifact.BlobCid.String(), artifact.Did)
163
if err != nil {
164
log.Println("failed to get blob from pds", err)
165
return
···
171
172
// TODO: proper statuses here on early exit
173
func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
174
-
user := s.auth.GetUser(r)
175
tagParam := chi.URLParam(r, "tag")
176
filename := chi.URLParam(r, "file")
177
f, err := s.fullyResolvedRepo(r)
···
180
return
181
}
182
183
-
client, _ := s.auth.AuthorizedClient(r)
184
185
tag := plumbing.NewHash(tagParam)
186
···
208
return
209
}
210
211
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
212
Collection: tangled.RepoArtifactNSID,
213
Repo: user.Did,
214
Rkey: artifact.Rkey,
···
254
return nil, err
255
}
256
257
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
258
if err != nil {
259
return nil, err
260
}
···
22
23
// TODO: proper statuses here on early exit
24
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
25
+
user := s.oauth.GetUser(r)
26
tagParam := chi.URLParam(r, "tag")
27
f, err := s.fullyResolvedRepo(r)
28
if err != nil {
···
46
}
47
defer file.Close()
48
49
+
client, err := s.oauth.AuthorizedClient(r)
50
+
if err != nil {
51
+
log.Println("failed to get authorized client", err)
52
+
s.pages.Notice(w, "upload", "failed to get authorized client")
53
+
return
54
+
}
55
56
+
uploadBlobResp, err := client.RepoUploadBlob(r.Context(), file)
57
if err != nil {
58
log.Println("failed to upload blob", err)
59
s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.")
···
65
rkey := appview.TID()
66
createdAt := time.Now()
67
68
+
putRecordResp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
69
Collection: tangled.RepoArtifactNSID,
70
Repo: user.Did,
71
Rkey: rkey,
···
145
return
146
}
147
148
+
client, err := s.oauth.AuthorizedClient(r)
149
+
if err != nil {
150
+
log.Println("failed to get authorized client", err)
151
+
return
152
+
}
153
154
artifacts, err := db.GetArtifact(
155
s.db,
···
168
169
artifact := artifacts[0]
170
171
+
getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did)
172
if err != nil {
173
log.Println("failed to get blob from pds", err)
174
return
···
180
181
// TODO: proper statuses here on early exit
182
func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
183
+
user := s.oauth.GetUser(r)
184
tagParam := chi.URLParam(r, "tag")
185
filename := chi.URLParam(r, "file")
186
f, err := s.fullyResolvedRepo(r)
···
189
return
190
}
191
192
+
client, _ := s.oauth.AuthorizedClient(r)
193
194
tag := plumbing.NewHash(tagParam)
195
···
217
return
218
}
219
220
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
221
Collection: tangled.RepoArtifactNSID,
222
Repo: user.Did,
223
Rkey: artifact.Rkey,
···
263
return nil, err
264
}
265
266
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
267
if err != nil {
268
return nil, err
269
}
+8
-4
appview/state/follow.go
+8
-4
appview/state/follow.go
···
14
)
15
16
func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
17
-
currentUser := s.auth.GetUser(r)
18
19
subject := r.URL.Query().Get("subject")
20
if subject == "" {
···
32
return
33
}
34
35
-
client, _ := s.auth.AuthorizedClient(r)
36
37
switch r.Method {
38
case http.MethodPost:
39
createdAt := time.Now().Format(time.RFC3339)
40
rkey := appview.TID()
41
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
42
Collection: tangled.GraphFollowNSID,
43
Repo: currentUser.Did,
44
Rkey: rkey,
···
75
return
76
}
77
78
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
79
Collection: tangled.GraphFollowNSID,
80
Repo: currentUser.Did,
81
Rkey: follow.Rkey,
···
14
)
15
16
func (s *State) Follow(w http.ResponseWriter, r *http.Request) {
17
+
currentUser := s.oauth.GetUser(r)
18
19
subject := r.URL.Query().Get("subject")
20
if subject == "" {
···
32
return
33
}
34
35
+
client, err := s.oauth.AuthorizedClient(r)
36
+
if err != nil {
37
+
log.Println("failed to authorize client")
38
+
return
39
+
}
40
41
switch r.Method {
42
case http.MethodPost:
43
createdAt := time.Now().Format(time.RFC3339)
44
rkey := appview.TID()
45
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
46
Collection: tangled.GraphFollowNSID,
47
Repo: currentUser.Did,
48
Rkey: rkey,
···
79
return
80
}
81
82
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
83
Collection: tangled.GraphFollowNSID,
84
Repo: currentUser.Did,
85
Rkey: follow.Rkey,
+2
-2
appview/state/git_http.go
+2
-2
appview/state/git_http.go
···
15
repo := chi.URLParam(r, "repo")
16
17
scheme := "https"
18
-
if s.config.Dev {
19
scheme = "http"
20
}
21
targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
···
52
repo := chi.URLParam(r, "repo")
53
54
scheme := "https"
55
-
if s.config.Dev {
56
scheme = "http"
57
}
58
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
···
15
repo := chi.URLParam(r, "repo")
16
17
scheme := "https"
18
+
if s.config.Core.Dev {
19
scheme = "http"
20
}
21
targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
···
52
repo := chi.URLParam(r, "repo")
53
54
scheme := "https"
55
+
if s.config.Core.Dev {
56
scheme = "http"
57
}
58
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)
+2
-2
appview/state/middleware.go
+2
-2
appview/state/middleware.go
···
20
return func(next http.Handler) http.Handler {
21
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
// requires auth also
23
-
actor := s.auth.GetUser(r)
24
if actor == nil {
25
// we need a logged in user
26
log.Printf("not logged in, redirecting")
···
54
return func(next http.Handler) http.Handler {
55
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56
// requires auth also
57
-
actor := s.auth.GetUser(r)
58
if actor == nil {
59
// we need a logged in user
60
log.Printf("not logged in, redirecting")
···
20
return func(next http.Handler) http.Handler {
21
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
// requires auth also
23
+
actor := s.oauth.GetUser(r)
24
if actor == nil {
25
// we need a logged in user
26
log.Printf("not logged in, redirecting")
···
54
return func(next http.Handler) http.Handler {
55
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56
// requires auth also
57
+
actor := s.oauth.GetUser(r)
58
if actor == nil {
59
// we need a logged in user
60
log.Printf("not logged in, redirecting")
+17
-12
appview/state/profile.go
+17
-12
appview/state/profile.go
···
119
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
120
}
121
122
-
loggedInUser := s.auth.GetUser(r)
123
followStatus := db.IsNotFollowing
124
if loggedInUser != nil {
125
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
···
161
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
162
}
163
164
-
loggedInUser := s.auth.GetUser(r)
165
followStatus := db.IsNotFollowing
166
if loggedInUser != nil {
167
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
···
190
}
191
192
func (s *State) GetAvatarUri(handle string) string {
193
-
secret := s.config.AvatarSharedSecret
194
h := hmac.New(sha256.New, []byte(secret))
195
h.Write([]byte(handle))
196
signature := hex.EncodeToString(h.Sum(nil))
197
-
return fmt.Sprintf("%s/%s/%s", s.config.AvatarHost, signature, handle)
198
}
199
200
func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) {
201
-
user := s.auth.GetUser(r)
202
203
err := r.ParseForm()
204
if err != nil {
···
246
}
247
248
func (s *State) UpdateProfilePins(w http.ResponseWriter, r *http.Request) {
249
-
user := s.auth.GetUser(r)
250
251
err := r.ParseForm()
252
if err != nil {
···
286
}
287
288
func (s *State) updateProfile(profile *db.Profile, w http.ResponseWriter, r *http.Request) {
289
-
user := s.auth.GetUser(r)
290
tx, err := s.db.BeginTx(r.Context(), nil)
291
if err != nil {
292
log.Println("failed to start transaction", err)
···
294
return
295
}
296
297
-
client, _ := s.auth.AuthorizedClient(r)
298
299
// yeah... lexgen dose not support syntax.ATURI in the record for some reason,
300
// nor does it support exact size arrays
···
308
vanityStats = append(vanityStats, string(v.Kind))
309
}
310
311
-
ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.ActorProfileNSID, user.Did, "self")
312
var cid *string
313
if ex != nil {
314
cid = ex.Cid
315
}
316
317
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
318
Collection: tangled.ActorProfileNSID,
319
Repo: user.Did,
320
Rkey: "self",
···
347
}
348
349
func (s *State) EditBioFragment(w http.ResponseWriter, r *http.Request) {
350
-
user := s.auth.GetUser(r)
351
352
profile, err := db.GetProfile(s.db, user.Did)
353
if err != nil {
···
361
}
362
363
func (s *State) EditPinsFragment(w http.ResponseWriter, r *http.Request) {
364
-
user := s.auth.GetUser(r)
365
366
profile, err := db.GetProfile(s.db, user.Did)
367
if err != nil {
···
119
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
120
}
121
122
+
loggedInUser := s.oauth.GetUser(r)
123
followStatus := db.IsNotFollowing
124
if loggedInUser != nil {
125
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
···
161
log.Printf("getting repos for %s: %s", ident.DID.String(), err)
162
}
163
164
+
loggedInUser := s.oauth.GetUser(r)
165
followStatus := db.IsNotFollowing
166
if loggedInUser != nil {
167
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
···
190
}
191
192
func (s *State) GetAvatarUri(handle string) string {
193
+
secret := s.config.Avatar.SharedSecret
194
h := hmac.New(sha256.New, []byte(secret))
195
h.Write([]byte(handle))
196
signature := hex.EncodeToString(h.Sum(nil))
197
+
return fmt.Sprintf("%s/%s/%s", s.config.Avatar.Host, signature, handle)
198
}
199
200
func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) {
201
+
user := s.oauth.GetUser(r)
202
203
err := r.ParseForm()
204
if err != nil {
···
246
}
247
248
func (s *State) UpdateProfilePins(w http.ResponseWriter, r *http.Request) {
249
+
user := s.oauth.GetUser(r)
250
251
err := r.ParseForm()
252
if err != nil {
···
286
}
287
288
func (s *State) updateProfile(profile *db.Profile, w http.ResponseWriter, r *http.Request) {
289
+
user := s.oauth.GetUser(r)
290
tx, err := s.db.BeginTx(r.Context(), nil)
291
if err != nil {
292
log.Println("failed to start transaction", err)
···
294
return
295
}
296
297
+
client, err := s.oauth.AuthorizedClient(r)
298
+
if err != nil {
299
+
log.Println("failed to get authorized client", err)
300
+
s.pages.Notice(w, "update-profile", "Failed to update profile, try again later.")
301
+
return
302
+
}
303
304
// yeah... lexgen dose not support syntax.ATURI in the record for some reason,
305
// nor does it support exact size arrays
···
313
vanityStats = append(vanityStats, string(v.Kind))
314
}
315
316
+
ex, _ := client.RepoGetRecord(r.Context(), "", tangled.ActorProfileNSID, user.Did, "self")
317
var cid *string
318
if ex != nil {
319
cid = ex.Cid
320
}
321
322
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
323
Collection: tangled.ActorProfileNSID,
324
Repo: user.Did,
325
Rkey: "self",
···
352
}
353
354
func (s *State) EditBioFragment(w http.ResponseWriter, r *http.Request) {
355
+
user := s.oauth.GetUser(r)
356
357
profile, err := db.GetProfile(s.db, user.Did)
358
if err != nil {
···
366
}
367
368
func (s *State) EditPinsFragment(w http.ResponseWriter, r *http.Request) {
369
+
user := s.oauth.GetUser(r)
370
371
profile, err := db.GetProfile(s.db, user.Did)
372
if err != nil {
+76
-51
appview/state/pull.go
+76
-51
appview/state/pull.go
···
13
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview"
16
-
"tangled.sh/tangled.sh/core/appview/auth"
17
"tangled.sh/tangled.sh/core/appview/db"
18
"tangled.sh/tangled.sh/core/appview/pages"
19
"tangled.sh/tangled.sh/core/patchutil"
20
"tangled.sh/tangled.sh/core/types"
···
29
func (s *State) PullActions(w http.ResponseWriter, r *http.Request) {
30
switch r.Method {
31
case http.MethodGet:
32
-
user := s.auth.GetUser(r)
33
f, err := s.fullyResolvedRepo(r)
34
if err != nil {
35
log.Println("failed to get repo and knot", err)
···
73
}
74
75
func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) {
76
-
user := s.auth.GetUser(r)
77
f, err := s.fullyResolvedRepo(r)
78
if err != nil {
79
log.Println("failed to get repo and knot", err)
···
143
}
144
}
145
146
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
147
if err != nil {
148
log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err)
149
return types.MergeCheckResponse{
···
215
repoName = f.RepoName
216
}
217
218
-
us, err := NewUnsignedClient(knot, s.config.Dev)
219
if err != nil {
220
log.Printf("failed to setup client for %s; ignoring: %v", knot, err)
221
return pages.Unknown
···
250
}
251
252
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
253
-
user := s.auth.GetUser(r)
254
f, err := s.fullyResolvedRepo(r)
255
if err != nil {
256
log.Println("failed to get repo and knot", err)
···
298
}
299
300
func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) {
301
-
user := s.auth.GetUser(r)
302
303
f, err := s.fullyResolvedRepo(r)
304
if err != nil {
···
355
interdiff := patchutil.Interdiff(previousPatch, currentPatch)
356
357
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
358
-
LoggedInUser: s.auth.GetUser(r),
359
RepoInfo: f.RepoInfo(s, user),
360
Pull: pull,
361
Round: roundIdInt,
···
397
}
398
399
func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) {
400
-
user := s.auth.GetUser(r)
401
params := r.URL.Query()
402
403
state := db.PullOpen
···
451
}
452
453
s.pages.RepoPulls(w, pages.RepoPullsParams{
454
-
LoggedInUser: s.auth.GetUser(r),
455
RepoInfo: f.RepoInfo(s, user),
456
Pulls: pulls,
457
DidHandleMap: didHandleMap,
···
461
}
462
463
func (s *State) PullComment(w http.ResponseWriter, r *http.Request) {
464
-
user := s.auth.GetUser(r)
465
f, err := s.fullyResolvedRepo(r)
466
if err != nil {
467
log.Println("failed to get repo and knot", err)
···
519
}
520
521
atUri := f.RepoAt.String()
522
-
client, _ := s.auth.AuthorizedClient(r)
523
-
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
524
Collection: tangled.RepoPullCommentNSID,
525
Repo: user.Did,
526
Rkey: appview.TID(),
···
568
}
569
570
func (s *State) NewPull(w http.ResponseWriter, r *http.Request) {
571
-
user := s.auth.GetUser(r)
572
f, err := s.fullyResolvedRepo(r)
573
if err != nil {
574
log.Println("failed to get repo and knot", err)
···
577
578
switch r.Method {
579
case http.MethodGet:
580
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
581
if err != nil {
582
log.Printf("failed to create unsigned client for %s", f.Knot)
583
s.pages.Error503(w)
···
646
return
647
}
648
649
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
650
if err != nil {
651
log.Printf("failed to create unsigned client to %s: %v", f.Knot, err)
652
s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.")
···
689
}
690
}
691
692
-
func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) {
693
pullSource := &db.PullSource{
694
Branch: sourceBranch,
695
}
···
698
}
699
700
// Generate a patch using /compare
701
-
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
702
if err != nil {
703
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
704
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
723
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)
724
}
725
726
-
func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) {
727
if !patchutil.IsPatchValid(patch) {
728
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
729
return
···
732
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil)
733
}
734
735
-
func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) {
736
fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)
737
if errors.Is(err, sql.ErrNoRows) {
738
s.pages.Notice(w, "pull", "No such fork.")
···
750
return
751
}
752
753
-
sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev)
754
if err != nil {
755
log.Println("failed to create signed client:", err)
756
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
757
return
758
}
759
760
-
us, err := NewUnsignedClient(fork.Knot, s.config.Dev)
761
if err != nil {
762
log.Println("failed to create unsigned client:", err)
763
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
816
w http.ResponseWriter,
817
r *http.Request,
818
f *FullyResolvedRepo,
819
-
user *auth.User,
820
title, body, targetBranch string,
821
patch string,
822
sourceRev string,
···
870
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
871
return
872
}
873
-
client, _ := s.auth.AuthorizedClient(r)
874
pullId, err := db.NextPullId(s.db, f.RepoAt)
875
if err != nil {
876
log.Println("failed to get pull id", err)
···
878
return
879
}
880
881
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
882
Collection: tangled.RepoPullNSID,
883
Repo: user.Did,
884
Rkey: rkey,
···
929
}
930
931
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
932
-
user := s.auth.GetUser(r)
933
f, err := s.fullyResolvedRepo(r)
934
if err != nil {
935
log.Println("failed to get repo and knot", err)
···
942
}
943
944
func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) {
945
-
user := s.auth.GetUser(r)
946
f, err := s.fullyResolvedRepo(r)
947
if err != nil {
948
log.Println("failed to get repo and knot", err)
949
return
950
}
951
952
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
953
if err != nil {
954
log.Printf("failed to create unsigned client for %s", f.Knot)
955
s.pages.Error503(w)
···
982
}
983
984
func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) {
985
-
user := s.auth.GetUser(r)
986
f, err := s.fullyResolvedRepo(r)
987
if err != nil {
988
log.Println("failed to get repo and knot", err)
···
1002
}
1003
1004
func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) {
1005
-
user := s.auth.GetUser(r)
1006
1007
f, err := s.fullyResolvedRepo(r)
1008
if err != nil {
···
1019
return
1020
}
1021
1022
-
sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Dev)
1023
if err != nil {
1024
log.Printf("failed to create unsigned client for %s", repo.Knot)
1025
s.pages.Error503(w)
···
1046
return
1047
}
1048
1049
-
targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
1050
if err != nil {
1051
log.Printf("failed to create unsigned client for target knot %s", f.Knot)
1052
s.pages.Error503(w)
···
1081
}
1082
1083
func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) {
1084
-
user := s.auth.GetUser(r)
1085
f, err := s.fullyResolvedRepo(r)
1086
if err != nil {
1087
log.Println("failed to get repo and knot", err)
···
1117
}
1118
1119
func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) {
1120
-
user := s.auth.GetUser(r)
1121
1122
pull, ok := r.Context().Value("pull").(*db.Pull)
1123
if !ok {
···
1159
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.")
1160
return
1161
}
1162
-
client, _ := s.auth.AuthorizedClient(r)
1163
1164
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1165
if err != nil {
1166
// failed to get record
1167
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
1168
return
1169
}
1170
1171
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1172
Collection: tangled.RepoPullNSID,
1173
Repo: user.Did,
1174
Rkey: pull.Rkey,
···
1200
}
1201
1202
func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) {
1203
-
user := s.auth.GetUser(r)
1204
1205
pull, ok := r.Context().Value("pull").(*db.Pull)
1206
if !ok {
···
1227
return
1228
}
1229
1230
-
ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev)
1231
if err != nil {
1232
log.Printf("failed to create client for %s: %s", f.Knot, err)
1233
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1268
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1269
return
1270
}
1271
-
client, _ := s.auth.AuthorizedClient(r)
1272
1273
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1274
if err != nil {
1275
// failed to get record
1276
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
···
1280
recordPullSource := &tangled.RepoPull_Source{
1281
Branch: pull.PullSource.Branch,
1282
}
1283
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1284
Collection: tangled.RepoPullNSID,
1285
Repo: user.Did,
1286
Rkey: pull.Rkey,
···
1313
}
1314
1315
func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) {
1316
-
user := s.auth.GetUser(r)
1317
1318
pull, ok := r.Context().Value("pull").(*db.Pull)
1319
if !ok {
···
1342
}
1343
1344
// extract patch by performing compare
1345
-
ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Dev)
1346
if err != nil {
1347
log.Printf("failed to create client for %s: %s", forkRepo.Knot, err)
1348
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1357
}
1358
1359
// update the hidden tracking branch to latest
1360
-
signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Dev)
1361
if err != nil {
1362
log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err)
1363
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1406
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1407
return
1408
}
1409
-
client, _ := s.auth.AuthorizedClient(r)
1410
1411
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1412
if err != nil {
1413
// failed to get record
1414
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
···
1420
Branch: pull.PullSource.Branch,
1421
Repo: &repoAt,
1422
}
1423
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1424
Collection: tangled.RepoPullNSID,
1425
Repo: user.Did,
1426
Rkey: pull.Rkey,
···
1503
log.Printf("failed to get primary email: %s", err)
1504
}
1505
1506
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
1507
if err != nil {
1508
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
1509
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
1533
}
1534
1535
func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) {
1536
-
user := s.auth.GetUser(r)
1537
1538
f, err := s.fullyResolvedRepo(r)
1539
if err != nil {
···
1587
}
1588
1589
func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) {
1590
-
user := s.auth.GetUser(r)
1591
1592
f, err := s.fullyResolvedRepo(r)
1593
if err != nil {
···
13
14
"tangled.sh/tangled.sh/core/api/tangled"
15
"tangled.sh/tangled.sh/core/appview"
16
"tangled.sh/tangled.sh/core/appview/db"
17
+
"tangled.sh/tangled.sh/core/appview/oauth"
18
"tangled.sh/tangled.sh/core/appview/pages"
19
"tangled.sh/tangled.sh/core/patchutil"
20
"tangled.sh/tangled.sh/core/types"
···
29
func (s *State) PullActions(w http.ResponseWriter, r *http.Request) {
30
switch r.Method {
31
case http.MethodGet:
32
+
user := s.oauth.GetUser(r)
33
f, err := s.fullyResolvedRepo(r)
34
if err != nil {
35
log.Println("failed to get repo and knot", err)
···
73
}
74
75
func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) {
76
+
user := s.oauth.GetUser(r)
77
f, err := s.fullyResolvedRepo(r)
78
if err != nil {
79
log.Println("failed to get repo and knot", err)
···
143
}
144
}
145
146
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev)
147
if err != nil {
148
log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err)
149
return types.MergeCheckResponse{
···
215
repoName = f.RepoName
216
}
217
218
+
us, err := NewUnsignedClient(knot, s.config.Core.Dev)
219
if err != nil {
220
log.Printf("failed to setup client for %s; ignoring: %v", knot, err)
221
return pages.Unknown
···
250
}
251
252
func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) {
253
+
user := s.oauth.GetUser(r)
254
f, err := s.fullyResolvedRepo(r)
255
if err != nil {
256
log.Println("failed to get repo and knot", err)
···
298
}
299
300
func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) {
301
+
user := s.oauth.GetUser(r)
302
303
f, err := s.fullyResolvedRepo(r)
304
if err != nil {
···
355
interdiff := patchutil.Interdiff(previousPatch, currentPatch)
356
357
s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{
358
+
LoggedInUser: s.oauth.GetUser(r),
359
RepoInfo: f.RepoInfo(s, user),
360
Pull: pull,
361
Round: roundIdInt,
···
397
}
398
399
func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) {
400
+
user := s.oauth.GetUser(r)
401
params := r.URL.Query()
402
403
state := db.PullOpen
···
451
}
452
453
s.pages.RepoPulls(w, pages.RepoPullsParams{
454
+
LoggedInUser: s.oauth.GetUser(r),
455
RepoInfo: f.RepoInfo(s, user),
456
Pulls: pulls,
457
DidHandleMap: didHandleMap,
···
461
}
462
463
func (s *State) PullComment(w http.ResponseWriter, r *http.Request) {
464
+
user := s.oauth.GetUser(r)
465
f, err := s.fullyResolvedRepo(r)
466
if err != nil {
467
log.Println("failed to get repo and knot", err)
···
519
}
520
521
atUri := f.RepoAt.String()
522
+
client, err := s.oauth.AuthorizedClient(r)
523
+
if err != nil {
524
+
log.Println("failed to get authorized client", err)
525
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
526
+
return
527
+
}
528
+
atResp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
529
Collection: tangled.RepoPullCommentNSID,
530
Repo: user.Did,
531
Rkey: appview.TID(),
···
573
}
574
575
func (s *State) NewPull(w http.ResponseWriter, r *http.Request) {
576
+
user := s.oauth.GetUser(r)
577
f, err := s.fullyResolvedRepo(r)
578
if err != nil {
579
log.Println("failed to get repo and knot", err)
···
582
583
switch r.Method {
584
case http.MethodGet:
585
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
586
if err != nil {
587
log.Printf("failed to create unsigned client for %s", f.Knot)
588
s.pages.Error503(w)
···
651
return
652
}
653
654
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
655
if err != nil {
656
log.Printf("failed to create unsigned client to %s: %v", f.Knot, err)
657
s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.")
···
694
}
695
}
696
697
+
func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, title, body, targetBranch, sourceBranch string) {
698
pullSource := &db.PullSource{
699
Branch: sourceBranch,
700
}
···
703
}
704
705
// Generate a patch using /compare
706
+
ksClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
707
if err != nil {
708
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
709
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
728
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource)
729
}
730
731
+
func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, title, body, targetBranch, patch string) {
732
if !patchutil.IsPatchValid(patch) {
733
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
734
return
···
737
s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil)
738
}
739
740
+
func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *oauth.User, forkRepo string, title, body, targetBranch, sourceBranch string) {
741
fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)
742
if errors.Is(err, sql.ErrNoRows) {
743
s.pages.Notice(w, "pull", "No such fork.")
···
755
return
756
}
757
758
+
sc, err := NewSignedClient(fork.Knot, secret, s.config.Core.Dev)
759
if err != nil {
760
log.Println("failed to create signed client:", err)
761
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
762
return
763
}
764
765
+
us, err := NewUnsignedClient(fork.Knot, s.config.Core.Dev)
766
if err != nil {
767
log.Println("failed to create unsigned client:", err)
768
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
···
821
w http.ResponseWriter,
822
r *http.Request,
823
f *FullyResolvedRepo,
824
+
user *oauth.User,
825
title, body, targetBranch string,
826
patch string,
827
sourceRev string,
···
875
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
876
return
877
}
878
+
client, err := s.oauth.AuthorizedClient(r)
879
+
if err != nil {
880
+
log.Println("failed to get authorized client", err)
881
+
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
882
+
return
883
+
}
884
pullId, err := db.NextPullId(s.db, f.RepoAt)
885
if err != nil {
886
log.Println("failed to get pull id", err)
···
888
return
889
}
890
891
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
892
Collection: tangled.RepoPullNSID,
893
Repo: user.Did,
894
Rkey: rkey,
···
939
}
940
941
func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) {
942
+
user := s.oauth.GetUser(r)
943
f, err := s.fullyResolvedRepo(r)
944
if err != nil {
945
log.Println("failed to get repo and knot", err)
···
952
}
953
954
func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) {
955
+
user := s.oauth.GetUser(r)
956
f, err := s.fullyResolvedRepo(r)
957
if err != nil {
958
log.Println("failed to get repo and knot", err)
959
return
960
}
961
962
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
963
if err != nil {
964
log.Printf("failed to create unsigned client for %s", f.Knot)
965
s.pages.Error503(w)
···
992
}
993
994
func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) {
995
+
user := s.oauth.GetUser(r)
996
f, err := s.fullyResolvedRepo(r)
997
if err != nil {
998
log.Println("failed to get repo and knot", err)
···
1012
}
1013
1014
func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) {
1015
+
user := s.oauth.GetUser(r)
1016
1017
f, err := s.fullyResolvedRepo(r)
1018
if err != nil {
···
1029
return
1030
}
1031
1032
+
sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Core.Dev)
1033
if err != nil {
1034
log.Printf("failed to create unsigned client for %s", repo.Knot)
1035
s.pages.Error503(w)
···
1056
return
1057
}
1058
1059
+
targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
1060
if err != nil {
1061
log.Printf("failed to create unsigned client for target knot %s", f.Knot)
1062
s.pages.Error503(w)
···
1091
}
1092
1093
func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) {
1094
+
user := s.oauth.GetUser(r)
1095
f, err := s.fullyResolvedRepo(r)
1096
if err != nil {
1097
log.Println("failed to get repo and knot", err)
···
1127
}
1128
1129
func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) {
1130
+
user := s.oauth.GetUser(r)
1131
1132
pull, ok := r.Context().Value("pull").(*db.Pull)
1133
if !ok {
···
1169
s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.")
1170
return
1171
}
1172
+
client, err := s.oauth.AuthorizedClient(r)
1173
+
if err != nil {
1174
+
log.Println("failed to get authorized client", err)
1175
+
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1176
+
return
1177
+
}
1178
1179
+
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1180
if err != nil {
1181
// failed to get record
1182
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
1183
return
1184
}
1185
1186
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1187
Collection: tangled.RepoPullNSID,
1188
Repo: user.Did,
1189
Rkey: pull.Rkey,
···
1215
}
1216
1217
func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) {
1218
+
user := s.oauth.GetUser(r)
1219
1220
pull, ok := r.Context().Value("pull").(*db.Pull)
1221
if !ok {
···
1242
return
1243
}
1244
1245
+
ksClient, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
1246
if err != nil {
1247
log.Printf("failed to create client for %s: %s", f.Knot, err)
1248
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1283
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1284
return
1285
}
1286
+
client, err := s.oauth.AuthorizedClient(r)
1287
+
if err != nil {
1288
+
log.Println("failed to authorize client")
1289
+
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1290
+
return
1291
+
}
1292
1293
+
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1294
if err != nil {
1295
// failed to get record
1296
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
···
1300
recordPullSource := &tangled.RepoPull_Source{
1301
Branch: pull.PullSource.Branch,
1302
}
1303
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1304
Collection: tangled.RepoPullNSID,
1305
Repo: user.Did,
1306
Rkey: pull.Rkey,
···
1333
}
1334
1335
func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) {
1336
+
user := s.oauth.GetUser(r)
1337
1338
pull, ok := r.Context().Value("pull").(*db.Pull)
1339
if !ok {
···
1362
}
1363
1364
// extract patch by performing compare
1365
+
ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Core.Dev)
1366
if err != nil {
1367
log.Printf("failed to create client for %s: %s", forkRepo.Knot, err)
1368
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1377
}
1378
1379
// update the hidden tracking branch to latest
1380
+
signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Core.Dev)
1381
if err != nil {
1382
log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err)
1383
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
···
1426
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1427
return
1428
}
1429
+
client, err := s.oauth.AuthorizedClient(r)
1430
+
if err != nil {
1431
+
log.Println("failed to get client")
1432
+
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
1433
+
return
1434
+
}
1435
1436
+
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoPullNSID, user.Did, pull.Rkey)
1437
if err != nil {
1438
// failed to get record
1439
s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.")
···
1445
Branch: pull.PullSource.Branch,
1446
Repo: &repoAt,
1447
}
1448
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1449
Collection: tangled.RepoPullNSID,
1450
Repo: user.Did,
1451
Rkey: pull.Rkey,
···
1528
log.Printf("failed to get primary email: %s", err)
1529
}
1530
1531
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev)
1532
if err != nil {
1533
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
1534
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
1558
}
1559
1560
func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) {
1561
+
user := s.oauth.GetUser(r)
1562
1563
f, err := s.fullyResolvedRepo(r)
1564
if err != nil {
···
1612
}
1613
1614
func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) {
1615
+
user := s.oauth.GetUser(r)
1616
1617
f, err := s.fullyResolvedRepo(r)
1618
if err != nil {
+98
-60
appview/state/repo.go
+98
-60
appview/state/repo.go
···
18
19
"tangled.sh/tangled.sh/core/api/tangled"
20
"tangled.sh/tangled.sh/core/appview"
21
-
"tangled.sh/tangled.sh/core/appview/auth"
22
"tangled.sh/tangled.sh/core/appview/db"
23
"tangled.sh/tangled.sh/core/appview/pages"
24
"tangled.sh/tangled.sh/core/appview/pages/markup"
25
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
···
45
return
46
}
47
48
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
49
if err != nil {
50
log.Printf("failed to create unsigned client for %s", f.Knot)
51
s.pages.Error503(w)
···
119
120
emails := uniqueEmails(commitsTrunc)
121
122
-
user := s.auth.GetUser(r)
123
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
124
LoggedInUser: user,
125
RepoInfo: f.RepoInfo(s, user),
···
150
151
ref := chi.URLParam(r, "ref")
152
153
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
154
if err != nil {
155
log.Println("failed to create unsigned client", err)
156
return
···
190
tagMap[hash] = append(tagMap[hash], tag.Name)
191
}
192
193
-
user := s.auth.GetUser(r)
194
s.pages.RepoLog(w, pages.RepoLogParams{
195
LoggedInUser: user,
196
TagMap: tagMap,
···
209
return
210
}
211
212
-
user := s.auth.GetUser(r)
213
s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
214
RepoInfo: f.RepoInfo(s, user),
215
})
···
232
return
233
}
234
235
-
user := s.auth.GetUser(r)
236
237
switch r.Method {
238
case http.MethodGet:
···
241
})
242
return
243
case http.MethodPut:
244
-
user := s.auth.GetUser(r)
245
newDescription := r.FormValue("description")
246
-
client, _ := s.auth.AuthorizedClient(r)
247
248
// optimistic update
249
err = db.UpdateDescription(s.db, string(repoAt), newDescription)
···
256
// this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field
257
//
258
// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests
259
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, user.Did, rkey)
260
if err != nil {
261
// failed to get record
262
s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
263
return
264
}
265
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
266
Collection: tangled.RepoNSID,
267
Repo: user.Did,
268
Rkey: rkey,
···
303
}
304
ref := chi.URLParam(r, "ref")
305
protocol := "http"
306
-
if !s.config.Dev {
307
protocol = "https"
308
}
309
···
331
return
332
}
333
334
-
user := s.auth.GetUser(r)
335
s.pages.RepoCommit(w, pages.RepoCommitParams{
336
LoggedInUser: user,
337
RepoInfo: f.RepoInfo(s, user),
···
351
ref := chi.URLParam(r, "ref")
352
treePath := chi.URLParam(r, "*")
353
protocol := "http"
354
-
if !s.config.Dev {
355
protocol = "https"
356
}
357
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
···
380
return
381
}
382
383
-
user := s.auth.GetUser(r)
384
385
var breadcrumbs [][]string
386
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
···
411
return
412
}
413
414
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
415
if err != nil {
416
log.Println("failed to create unsigned client", err)
417
return
···
451
}
452
}
453
454
-
user := s.auth.GetUser(r)
455
s.pages.RepoTags(w, pages.RepoTagsParams{
456
LoggedInUser: user,
457
RepoInfo: f.RepoInfo(s, user),
···
469
return
470
}
471
472
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
473
if err != nil {
474
log.Println("failed to create unsigned client", err)
475
return
···
511
return strings.Compare(a.Name, b.Name) * -1
512
})
513
514
-
user := s.auth.GetUser(r)
515
s.pages.RepoBranches(w, pages.RepoBranchesParams{
516
LoggedInUser: user,
517
RepoInfo: f.RepoInfo(s, user),
···
530
ref := chi.URLParam(r, "ref")
531
filePath := chi.URLParam(r, "*")
532
protocol := "http"
533
-
if !s.config.Dev {
534
protocol = "https"
535
}
536
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
568
showRendered = r.URL.Query().Get("code") != "true"
569
}
570
571
-
user := s.auth.GetUser(r)
572
s.pages.RepoBlob(w, pages.RepoBlobParams{
573
LoggedInUser: user,
574
RepoInfo: f.RepoInfo(s, user),
···
591
filePath := chi.URLParam(r, "*")
592
593
protocol := "http"
594
-
if !s.config.Dev {
595
protocol = "https"
596
}
597
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
652
return
653
}
654
655
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
656
if err != nil {
657
log.Println("failed to create client to ", f.Knot)
658
return
···
714
}
715
716
func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) {
717
-
user := s.auth.GetUser(r)
718
719
f, err := s.fullyResolvedRepo(r)
720
if err != nil {
···
723
}
724
725
// remove record from pds
726
-
xrpcClient, _ := s.auth.AuthorizedClient(r)
727
repoRkey := f.RepoAt.RecordKey().String()
728
-
_, err = comatproto.RepoDeleteRecord(r.Context(), xrpcClient, &comatproto.RepoDeleteRecord_Input{
729
Collection: tangled.RepoNSID,
730
Repo: user.Did,
731
Rkey: repoRkey,
···
743
return
744
}
745
746
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
747
if err != nil {
748
log.Println("failed to create client to ", f.Knot)
749
return
···
838
return
839
}
840
841
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
842
if err != nil {
843
log.Println("failed to create client to ", f.Knot)
844
return
···
868
switch r.Method {
869
case http.MethodGet:
870
// for now, this is just pubkeys
871
-
user := s.auth.GetUser(r)
872
repoCollaborators, err := f.Collaborators(r.Context(), s)
873
if err != nil {
874
log.Println("failed to get collaborators", err)
···
884
885
var branchNames []string
886
var defaultBranch string
887
-
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
888
if err != nil {
889
log.Println("failed to create unsigned client", err)
890
} else {
···
1008
return collaborators, nil
1009
}
1010
1011
-
func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) repoinfo.RepoInfo {
1012
isStarred := false
1013
if u != nil {
1014
isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt))
···
1051
1052
knot := f.Knot
1053
var disableFork bool
1054
-
us, err := NewUnsignedClient(knot, s.config.Dev)
1055
if err != nil {
1056
log.Printf("failed to create unsigned client for %s: %v", knot, err)
1057
} else {
···
1105
}
1106
1107
func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
1108
-
user := s.auth.GetUser(r)
1109
f, err := s.fullyResolvedRepo(r)
1110
if err != nil {
1111
log.Println("failed to get repo and knot", err)
···
1159
}
1160
1161
func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
1162
-
user := s.auth.GetUser(r)
1163
f, err := s.fullyResolvedRepo(r)
1164
if err != nil {
1165
log.Println("failed to get repo and knot", err)
···
1195
1196
closed := tangled.RepoIssueStateClosed
1197
1198
-
client, _ := s.auth.AuthorizedClient(r)
1199
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1200
Collection: tangled.RepoIssueStateNSID,
1201
Repo: user.Did,
1202
Rkey: appview.TID(),
···
1214
return
1215
}
1216
1217
-
err := db.CloseIssue(s.db, f.RepoAt, issueIdInt)
1218
if err != nil {
1219
log.Println("failed to close issue", err)
1220
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
···
1231
}
1232
1233
func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
1234
-
user := s.auth.GetUser(r)
1235
f, err := s.fullyResolvedRepo(r)
1236
if err != nil {
1237
log.Println("failed to get repo and knot", err)
···
1279
}
1280
1281
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
1282
-
user := s.auth.GetUser(r)
1283
f, err := s.fullyResolvedRepo(r)
1284
if err != nil {
1285
log.Println("failed to get repo and knot", err)
···
1330
}
1331
1332
atUri := f.RepoAt.String()
1333
-
client, _ := s.auth.AuthorizedClient(r)
1334
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1335
Collection: tangled.RepoIssueCommentNSID,
1336
Repo: user.Did,
1337
Rkey: rkey,
···
1358
}
1359
1360
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
1361
-
user := s.auth.GetUser(r)
1362
f, err := s.fullyResolvedRepo(r)
1363
if err != nil {
1364
log.Println("failed to get repo and knot", err)
···
1417
}
1418
1419
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1420
-
user := s.auth.GetUser(r)
1421
f, err := s.fullyResolvedRepo(r)
1422
if err != nil {
1423
log.Println("failed to get repo and knot", err)
···
1469
case http.MethodPost:
1470
// extract form value
1471
newBody := r.FormValue("body")
1472
-
client, _ := s.auth.AuthorizedClient(r)
1473
rkey := comment.Rkey
1474
1475
// optimistic update
···
1484
// rkey is optional, it was introduced later
1485
if comment.Rkey != "" {
1486
// update the record on pds
1487
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey)
1488
if err != nil {
1489
// failed to get record
1490
log.Println(err, rkey)
···
1499
createdAt := record["createdAt"].(string)
1500
commentIdInt64 := int64(commentIdInt)
1501
1502
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1503
Collection: tangled.RepoIssueCommentNSID,
1504
Repo: user.Did,
1505
Rkey: rkey,
···
1542
}
1543
1544
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
1545
-
user := s.auth.GetUser(r)
1546
f, err := s.fullyResolvedRepo(r)
1547
if err != nil {
1548
log.Println("failed to get repo and knot", err)
···
1599
1600
// delete from pds
1601
if comment.Rkey != "" {
1602
-
client, _ := s.auth.AuthorizedClient(r)
1603
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
1604
Collection: tangled.GraphFollowNSID,
1605
Repo: user.Did,
1606
Rkey: comment.Rkey,
···
1647
page = pagination.FirstPage()
1648
}
1649
1650
-
user := s.auth.GetUser(r)
1651
f, err := s.fullyResolvedRepo(r)
1652
if err != nil {
1653
log.Println("failed to get repo and knot", err)
···
1676
}
1677
1678
s.pages.RepoIssues(w, pages.RepoIssuesParams{
1679
-
LoggedInUser: s.auth.GetUser(r),
1680
RepoInfo: f.RepoInfo(s, user),
1681
Issues: issues,
1682
DidHandleMap: didHandleMap,
···
1687
}
1688
1689
func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
1690
-
user := s.auth.GetUser(r)
1691
1692
f, err := s.fullyResolvedRepo(r)
1693
if err != nil {
···
1735
return
1736
}
1737
1738
-
client, _ := s.auth.AuthorizedClient(r)
1739
atUri := f.RepoAt.String()
1740
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1741
Collection: tangled.RepoIssueNSID,
1742
Repo: user.Did,
1743
Rkey: appview.TID(),
···
1770
}
1771
1772
func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) {
1773
-
user := s.auth.GetUser(r)
1774
f, err := s.fullyResolvedRepo(r)
1775
if err != nil {
1776
log.Printf("failed to resolve source repo: %v", err)
···
1779
1780
switch r.Method {
1781
case http.MethodGet:
1782
-
user := s.auth.GetUser(r)
1783
knots, err := s.enforcer.GetDomainsForUser(user.Did)
1784
if err != nil {
1785
s.pages.Notice(w, "repo", "Invalid user account.")
···
1829
return
1830
}
1831
1832
-
client, err := NewSignedClient(knot, secret, s.config.Dev)
1833
if err != nil {
1834
s.pages.Notice(w, "repo", "Failed to reach knot server.")
1835
return
1836
}
1837
1838
var uri string
1839
-
if s.config.Dev {
1840
uri = "http"
1841
} else {
1842
uri = "https"
···
1883
// continue
1884
}
1885
1886
-
xrpcClient, _ := s.auth.AuthorizedClient(r)
1887
1888
createdAt := time.Now().Format(time.RFC3339)
1889
-
atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{
1890
Collection: tangled.RepoNSID,
1891
Repo: user.Did,
1892
Rkey: rkey,
···
18
19
"tangled.sh/tangled.sh/core/api/tangled"
20
"tangled.sh/tangled.sh/core/appview"
21
"tangled.sh/tangled.sh/core/appview/db"
22
+
"tangled.sh/tangled.sh/core/appview/oauth"
23
"tangled.sh/tangled.sh/core/appview/pages"
24
"tangled.sh/tangled.sh/core/appview/pages/markup"
25
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
···
45
return
46
}
47
48
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
49
if err != nil {
50
log.Printf("failed to create unsigned client for %s", f.Knot)
51
s.pages.Error503(w)
···
119
120
emails := uniqueEmails(commitsTrunc)
121
122
+
user := s.oauth.GetUser(r)
123
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
124
LoggedInUser: user,
125
RepoInfo: f.RepoInfo(s, user),
···
150
151
ref := chi.URLParam(r, "ref")
152
153
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
154
if err != nil {
155
log.Println("failed to create unsigned client", err)
156
return
···
190
tagMap[hash] = append(tagMap[hash], tag.Name)
191
}
192
193
+
user := s.oauth.GetUser(r)
194
s.pages.RepoLog(w, pages.RepoLogParams{
195
LoggedInUser: user,
196
TagMap: tagMap,
···
209
return
210
}
211
212
+
user := s.oauth.GetUser(r)
213
s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
214
RepoInfo: f.RepoInfo(s, user),
215
})
···
232
return
233
}
234
235
+
user := s.oauth.GetUser(r)
236
237
switch r.Method {
238
case http.MethodGet:
···
241
})
242
return
243
case http.MethodPut:
244
+
user := s.oauth.GetUser(r)
245
newDescription := r.FormValue("description")
246
+
client, err := s.oauth.AuthorizedClient(r)
247
+
if err != nil {
248
+
log.Println("failed to get client")
249
+
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
250
+
return
251
+
}
252
253
// optimistic update
254
err = db.UpdateDescription(s.db, string(repoAt), newDescription)
···
261
// this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field
262
//
263
// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests
264
+
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey)
265
if err != nil {
266
// failed to get record
267
s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
268
return
269
}
270
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
271
Collection: tangled.RepoNSID,
272
Repo: user.Did,
273
Rkey: rkey,
···
308
}
309
ref := chi.URLParam(r, "ref")
310
protocol := "http"
311
+
if !s.config.Core.Dev {
312
protocol = "https"
313
}
314
···
336
return
337
}
338
339
+
user := s.oauth.GetUser(r)
340
s.pages.RepoCommit(w, pages.RepoCommitParams{
341
LoggedInUser: user,
342
RepoInfo: f.RepoInfo(s, user),
···
356
ref := chi.URLParam(r, "ref")
357
treePath := chi.URLParam(r, "*")
358
protocol := "http"
359
+
if !s.config.Core.Dev {
360
protocol = "https"
361
}
362
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
···
385
return
386
}
387
388
+
user := s.oauth.GetUser(r)
389
390
var breadcrumbs [][]string
391
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
···
416
return
417
}
418
419
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
420
if err != nil {
421
log.Println("failed to create unsigned client", err)
422
return
···
456
}
457
}
458
459
+
user := s.oauth.GetUser(r)
460
s.pages.RepoTags(w, pages.RepoTagsParams{
461
LoggedInUser: user,
462
RepoInfo: f.RepoInfo(s, user),
···
474
return
475
}
476
477
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
478
if err != nil {
479
log.Println("failed to create unsigned client", err)
480
return
···
516
return strings.Compare(a.Name, b.Name) * -1
517
})
518
519
+
user := s.oauth.GetUser(r)
520
s.pages.RepoBranches(w, pages.RepoBranchesParams{
521
LoggedInUser: user,
522
RepoInfo: f.RepoInfo(s, user),
···
535
ref := chi.URLParam(r, "ref")
536
filePath := chi.URLParam(r, "*")
537
protocol := "http"
538
+
if !s.config.Core.Dev {
539
protocol = "https"
540
}
541
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
573
showRendered = r.URL.Query().Get("code") != "true"
574
}
575
576
+
user := s.oauth.GetUser(r)
577
s.pages.RepoBlob(w, pages.RepoBlobParams{
578
LoggedInUser: user,
579
RepoInfo: f.RepoInfo(s, user),
···
596
filePath := chi.URLParam(r, "*")
597
598
protocol := "http"
599
+
if !s.config.Core.Dev {
600
protocol = "https"
601
}
602
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
657
return
658
}
659
660
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev)
661
if err != nil {
662
log.Println("failed to create client to ", f.Knot)
663
return
···
719
}
720
721
func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) {
722
+
user := s.oauth.GetUser(r)
723
724
f, err := s.fullyResolvedRepo(r)
725
if err != nil {
···
728
}
729
730
// remove record from pds
731
+
xrpcClient, err := s.oauth.AuthorizedClient(r)
732
+
if err != nil {
733
+
log.Println("failed to get authorized client", err)
734
+
return
735
+
}
736
repoRkey := f.RepoAt.RecordKey().String()
737
+
_, err = xrpcClient.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
738
Collection: tangled.RepoNSID,
739
Repo: user.Did,
740
Rkey: repoRkey,
···
752
return
753
}
754
755
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev)
756
if err != nil {
757
log.Println("failed to create client to ", f.Knot)
758
return
···
847
return
848
}
849
850
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Core.Dev)
851
if err != nil {
852
log.Println("failed to create client to ", f.Knot)
853
return
···
877
switch r.Method {
878
case http.MethodGet:
879
// for now, this is just pubkeys
880
+
user := s.oauth.GetUser(r)
881
repoCollaborators, err := f.Collaborators(r.Context(), s)
882
if err != nil {
883
log.Println("failed to get collaborators", err)
···
893
894
var branchNames []string
895
var defaultBranch string
896
+
us, err := NewUnsignedClient(f.Knot, s.config.Core.Dev)
897
if err != nil {
898
log.Println("failed to create unsigned client", err)
899
} else {
···
1017
return collaborators, nil
1018
}
1019
1020
+
func (f *FullyResolvedRepo) RepoInfo(s *State, u *oauth.User) repoinfo.RepoInfo {
1021
isStarred := false
1022
if u != nil {
1023
isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt))
···
1060
1061
knot := f.Knot
1062
var disableFork bool
1063
+
us, err := NewUnsignedClient(knot, s.config.Core.Dev)
1064
if err != nil {
1065
log.Printf("failed to create unsigned client for %s: %v", knot, err)
1066
} else {
···
1114
}
1115
1116
func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
1117
+
user := s.oauth.GetUser(r)
1118
f, err := s.fullyResolvedRepo(r)
1119
if err != nil {
1120
log.Println("failed to get repo and knot", err)
···
1168
}
1169
1170
func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
1171
+
user := s.oauth.GetUser(r)
1172
f, err := s.fullyResolvedRepo(r)
1173
if err != nil {
1174
log.Println("failed to get repo and knot", err)
···
1204
1205
closed := tangled.RepoIssueStateClosed
1206
1207
+
client, err := s.oauth.AuthorizedClient(r)
1208
+
if err != nil {
1209
+
log.Println("failed to get authorized client", err)
1210
+
return
1211
+
}
1212
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1213
Collection: tangled.RepoIssueStateNSID,
1214
Repo: user.Did,
1215
Rkey: appview.TID(),
···
1227
return
1228
}
1229
1230
+
err = db.CloseIssue(s.db, f.RepoAt, issueIdInt)
1231
if err != nil {
1232
log.Println("failed to close issue", err)
1233
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
···
1244
}
1245
1246
func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
1247
+
user := s.oauth.GetUser(r)
1248
f, err := s.fullyResolvedRepo(r)
1249
if err != nil {
1250
log.Println("failed to get repo and knot", err)
···
1292
}
1293
1294
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
1295
+
user := s.oauth.GetUser(r)
1296
f, err := s.fullyResolvedRepo(r)
1297
if err != nil {
1298
log.Println("failed to get repo and knot", err)
···
1343
}
1344
1345
atUri := f.RepoAt.String()
1346
+
client, err := s.oauth.AuthorizedClient(r)
1347
+
if err != nil {
1348
+
log.Println("failed to get authorized client", err)
1349
+
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1350
+
return
1351
+
}
1352
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1353
Collection: tangled.RepoIssueCommentNSID,
1354
Repo: user.Did,
1355
Rkey: rkey,
···
1376
}
1377
1378
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
1379
+
user := s.oauth.GetUser(r)
1380
f, err := s.fullyResolvedRepo(r)
1381
if err != nil {
1382
log.Println("failed to get repo and knot", err)
···
1435
}
1436
1437
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1438
+
user := s.oauth.GetUser(r)
1439
f, err := s.fullyResolvedRepo(r)
1440
if err != nil {
1441
log.Println("failed to get repo and knot", err)
···
1487
case http.MethodPost:
1488
// extract form value
1489
newBody := r.FormValue("body")
1490
+
client, err := s.oauth.AuthorizedClient(r)
1491
+
if err != nil {
1492
+
log.Println("failed to get authorized client", err)
1493
+
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1494
+
return
1495
+
}
1496
rkey := comment.Rkey
1497
1498
// optimistic update
···
1507
// rkey is optional, it was introduced later
1508
if comment.Rkey != "" {
1509
// update the record on pds
1510
+
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoIssueCommentNSID, user.Did, rkey)
1511
if err != nil {
1512
// failed to get record
1513
log.Println(err, rkey)
···
1522
createdAt := record["createdAt"].(string)
1523
commentIdInt64 := int64(commentIdInt)
1524
1525
+
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1526
Collection: tangled.RepoIssueCommentNSID,
1527
Repo: user.Did,
1528
Rkey: rkey,
···
1565
}
1566
1567
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
1568
+
user := s.oauth.GetUser(r)
1569
f, err := s.fullyResolvedRepo(r)
1570
if err != nil {
1571
log.Println("failed to get repo and knot", err)
···
1622
1623
// delete from pds
1624
if comment.Rkey != "" {
1625
+
client, err := s.oauth.AuthorizedClient(r)
1626
+
if err != nil {
1627
+
log.Println("failed to get authorized client", err)
1628
+
s.pages.Notice(w, "issue-comment", "Failed to delete comment.")
1629
+
return
1630
+
}
1631
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
1632
Collection: tangled.GraphFollowNSID,
1633
Repo: user.Did,
1634
Rkey: comment.Rkey,
···
1675
page = pagination.FirstPage()
1676
}
1677
1678
+
user := s.oauth.GetUser(r)
1679
f, err := s.fullyResolvedRepo(r)
1680
if err != nil {
1681
log.Println("failed to get repo and knot", err)
···
1704
}
1705
1706
s.pages.RepoIssues(w, pages.RepoIssuesParams{
1707
+
LoggedInUser: s.oauth.GetUser(r),
1708
RepoInfo: f.RepoInfo(s, user),
1709
Issues: issues,
1710
DidHandleMap: didHandleMap,
···
1715
}
1716
1717
func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
1718
+
user := s.oauth.GetUser(r)
1719
1720
f, err := s.fullyResolvedRepo(r)
1721
if err != nil {
···
1763
return
1764
}
1765
1766
+
client, err := s.oauth.AuthorizedClient(r)
1767
+
if err != nil {
1768
+
log.Println("failed to get authorized client", err)
1769
+
s.pages.Notice(w, "issues", "Failed to create issue.")
1770
+
return
1771
+
}
1772
atUri := f.RepoAt.String()
1773
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1774
Collection: tangled.RepoIssueNSID,
1775
Repo: user.Did,
1776
Rkey: appview.TID(),
···
1803
}
1804
1805
func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) {
1806
+
user := s.oauth.GetUser(r)
1807
f, err := s.fullyResolvedRepo(r)
1808
if err != nil {
1809
log.Printf("failed to resolve source repo: %v", err)
···
1812
1813
switch r.Method {
1814
case http.MethodGet:
1815
+
user := s.oauth.GetUser(r)
1816
knots, err := s.enforcer.GetDomainsForUser(user.Did)
1817
if err != nil {
1818
s.pages.Notice(w, "repo", "Invalid user account.")
···
1862
return
1863
}
1864
1865
+
client, err := NewSignedClient(knot, secret, s.config.Core.Dev)
1866
if err != nil {
1867
s.pages.Notice(w, "repo", "Failed to reach knot server.")
1868
return
1869
}
1870
1871
var uri string
1872
+
if s.config.Core.Dev {
1873
uri = "http"
1874
} else {
1875
uri = "https"
···
1916
// continue
1917
}
1918
1919
+
xrpcClient, err := s.oauth.AuthorizedClient(r)
1920
+
if err != nil {
1921
+
log.Println("failed to get authorized client", err)
1922
+
s.pages.Notice(w, "repo", "Failed to create repository.")
1923
+
return
1924
+
}
1925
1926
createdAt := time.Now().Format(time.RFC3339)
1927
+
atresp, err := xrpcClient.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
1928
Collection: tangled.RepoNSID,
1929
Repo: user.Did,
1930
Rkey: rkey,
+3
-3
appview/state/repo_util.go
+3
-3
appview/state/repo_util.go
···
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
"github.com/go-chi/chi/v5"
14
"github.com/go-git/go-git/v5/plumbing/object"
15
-
"tangled.sh/tangled.sh/core/appview/auth"
16
"tangled.sh/tangled.sh/core/appview/db"
17
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
18
)
19
···
45
ref := chi.URLParam(r, "ref")
46
47
if ref == "" {
48
-
us, err := NewUnsignedClient(knot, s.config.Dev)
49
if err != nil {
50
return nil, err
51
}
···
73
}, nil
74
}
75
76
-
func RolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo {
77
if u != nil {
78
r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo())
79
return repoinfo.RolesInRepo{r}
···
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
"github.com/go-chi/chi/v5"
14
"github.com/go-git/go-git/v5/plumbing/object"
15
"tangled.sh/tangled.sh/core/appview/db"
16
+
"tangled.sh/tangled.sh/core/appview/oauth"
17
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
18
)
19
···
45
ref := chi.URLParam(r, "ref")
46
47
if ref == "" {
48
+
us, err := NewUnsignedClient(knot, s.config.Core.Dev)
49
if err != nil {
50
return nil, err
51
}
···
73
}, nil
74
}
75
76
+
func RolesInRepo(s *State, u *oauth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo {
77
if u != nil {
78
r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo())
79
return repoinfo.RolesInRepo{r}
+34
-19
appview/state/router.go
+34
-19
appview/state/router.go
···
5
"strings"
6
7
"github.com/go-chi/chi/v5"
8
"tangled.sh/tangled.sh/core/appview/middleware"
9
"tangled.sh/tangled.sh/core/appview/settings"
10
"tangled.sh/tangled.sh/core/appview/state/userutil"
11
)
···
67
r.Route("/tags", func(r chi.Router) {
68
r.Get("/", s.RepoTags)
69
r.Route("/{tag}", func(r chi.Router) {
70
-
r.Use(middleware.AuthMiddleware(s.auth))
71
// require auth to download for now
72
r.Get("/download/{file}", s.DownloadArtifact)
73
···
90
r.Get("/{issue}", s.RepoSingleIssue)
91
92
r.Group(func(r chi.Router) {
93
-
r.Use(middleware.AuthMiddleware(s.auth))
94
r.Get("/new", s.NewIssue)
95
r.Post("/new", s.NewIssue)
96
r.Post("/{issue}/comment", s.NewIssueComment)
···
106
})
107
108
r.Route("/fork", func(r chi.Router) {
109
-
r.Use(middleware.AuthMiddleware(s.auth))
110
r.Get("/", s.ForkRepo)
111
r.Post("/", s.ForkRepo)
112
})
113
114
r.Route("/pulls", func(r chi.Router) {
115
r.Get("/", s.RepoPulls)
116
-
r.With(middleware.AuthMiddleware(s.auth)).Route("/new", func(r chi.Router) {
117
r.Get("/", s.NewPull)
118
r.Get("/patch-upload", s.PatchUploadFragment)
119
r.Post("/validate-patch", s.ValidatePatch)
···
131
r.Get("/", s.RepoPullPatch)
132
r.Get("/interdiff", s.RepoPullInterdiff)
133
r.Get("/actions", s.PullActions)
134
-
r.With(middleware.AuthMiddleware(s.auth)).Route("/comment", func(r chi.Router) {
135
r.Get("/", s.PullComment)
136
r.Post("/", s.PullComment)
137
})
···
142
})
143
144
r.Group(func(r chi.Router) {
145
-
r.Use(middleware.AuthMiddleware(s.auth))
146
r.Route("/resubmit", func(r chi.Router) {
147
r.Get("/", s.ResubmitPull)
148
r.Post("/", s.ResubmitPull)
···
165
166
// settings routes, needs auth
167
r.Group(func(r chi.Router) {
168
-
r.Use(middleware.AuthMiddleware(s.auth))
169
// repo description can only be edited by owner
170
r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/description", func(r chi.Router) {
171
r.Put("/", s.RepoDescription)
···
196
197
r.Get("/", s.Timeline)
198
199
-
r.With(middleware.AuthMiddleware(s.auth)).Post("/logout", s.Logout)
200
201
-
r.Route("/login", func(r chi.Router) {
202
-
r.Get("/", s.Login)
203
-
r.Post("/", s.Login)
204
-
})
205
206
r.Route("/knots", func(r chi.Router) {
207
-
r.Use(middleware.AuthMiddleware(s.auth))
208
r.Get("/", s.Knots)
209
r.Post("/key", s.RegistrationKey)
210
···
222
223
r.Route("/repo", func(r chi.Router) {
224
r.Route("/new", func(r chi.Router) {
225
-
r.Use(middleware.AuthMiddleware(s.auth))
226
r.Get("/", s.NewRepo)
227
r.Post("/", s.NewRepo)
228
})
229
// r.Post("/import", s.ImportRepo)
230
})
231
232
-
r.With(middleware.AuthMiddleware(s.auth)).Route("/follow", func(r chi.Router) {
233
r.Post("/", s.Follow)
234
r.Delete("/", s.Follow)
235
})
236
237
-
r.With(middleware.AuthMiddleware(s.auth)).Route("/star", func(r chi.Router) {
238
r.Post("/", s.Star)
239
r.Delete("/", s.Star)
240
})
241
242
r.Route("/profile", func(r chi.Router) {
243
-
r.Use(middleware.AuthMiddleware(s.auth))
244
r.Get("/edit-bio", s.EditBioFragment)
245
r.Get("/edit-pins", s.EditPinsFragment)
246
r.Post("/bio", s.UpdateProfileBio)
···
248
})
249
250
r.Mount("/settings", s.SettingsRouter())
251
-
252
r.Get("/keys/{user}", s.Keys)
253
254
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
···
257
return r
258
}
259
260
func (s *State) SettingsRouter() http.Handler {
261
settings := &settings.Settings{
262
Db: s.db,
263
-
Auth: s.auth,
264
Pages: s.pages,
265
Config: s.config,
266
}
···
5
"strings"
6
7
"github.com/go-chi/chi/v5"
8
+
"github.com/gorilla/sessions"
9
"tangled.sh/tangled.sh/core/appview/middleware"
10
+
oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler"
11
"tangled.sh/tangled.sh/core/appview/settings"
12
"tangled.sh/tangled.sh/core/appview/state/userutil"
13
)
···
69
r.Route("/tags", func(r chi.Router) {
70
r.Get("/", s.RepoTags)
71
r.Route("/{tag}", func(r chi.Router) {
72
+
r.Use(middleware.AuthMiddleware(s.oauth))
73
// require auth to download for now
74
r.Get("/download/{file}", s.DownloadArtifact)
75
···
92
r.Get("/{issue}", s.RepoSingleIssue)
93
94
r.Group(func(r chi.Router) {
95
+
r.Use(middleware.AuthMiddleware(s.oauth))
96
r.Get("/new", s.NewIssue)
97
r.Post("/new", s.NewIssue)
98
r.Post("/{issue}/comment", s.NewIssueComment)
···
108
})
109
110
r.Route("/fork", func(r chi.Router) {
111
+
r.Use(middleware.AuthMiddleware(s.oauth))
112
r.Get("/", s.ForkRepo)
113
r.Post("/", s.ForkRepo)
114
})
115
116
r.Route("/pulls", func(r chi.Router) {
117
r.Get("/", s.RepoPulls)
118
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/new", func(r chi.Router) {
119
r.Get("/", s.NewPull)
120
r.Get("/patch-upload", s.PatchUploadFragment)
121
r.Post("/validate-patch", s.ValidatePatch)
···
133
r.Get("/", s.RepoPullPatch)
134
r.Get("/interdiff", s.RepoPullInterdiff)
135
r.Get("/actions", s.PullActions)
136
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/comment", func(r chi.Router) {
137
r.Get("/", s.PullComment)
138
r.Post("/", s.PullComment)
139
})
···
144
})
145
146
r.Group(func(r chi.Router) {
147
+
r.Use(middleware.AuthMiddleware(s.oauth))
148
r.Route("/resubmit", func(r chi.Router) {
149
r.Get("/", s.ResubmitPull)
150
r.Post("/", s.ResubmitPull)
···
167
168
// settings routes, needs auth
169
r.Group(func(r chi.Router) {
170
+
r.Use(middleware.AuthMiddleware(s.oauth))
171
// repo description can only be edited by owner
172
r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/description", func(r chi.Router) {
173
r.Put("/", s.RepoDescription)
···
198
199
r.Get("/", s.Timeline)
200
201
+
r.With(middleware.AuthMiddleware(s.oauth)).Post("/logout", s.Logout)
202
203
+
// r.Route("/login", func(r chi.Router) {
204
+
// r.Get("/", s.Login)
205
+
// r.Post("/", s.Login)
206
+
// })
207
208
r.Route("/knots", func(r chi.Router) {
209
+
r.Use(middleware.AuthMiddleware(s.oauth))
210
r.Get("/", s.Knots)
211
r.Post("/key", s.RegistrationKey)
212
···
224
225
r.Route("/repo", func(r chi.Router) {
226
r.Route("/new", func(r chi.Router) {
227
+
r.Use(middleware.AuthMiddleware(s.oauth))
228
r.Get("/", s.NewRepo)
229
r.Post("/", s.NewRepo)
230
})
231
// r.Post("/import", s.ImportRepo)
232
})
233
234
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/follow", func(r chi.Router) {
235
r.Post("/", s.Follow)
236
r.Delete("/", s.Follow)
237
})
238
239
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/star", func(r chi.Router) {
240
r.Post("/", s.Star)
241
r.Delete("/", s.Star)
242
})
243
244
r.Route("/profile", func(r chi.Router) {
245
+
r.Use(middleware.AuthMiddleware(s.oauth))
246
r.Get("/edit-bio", s.EditBioFragment)
247
r.Get("/edit-pins", s.EditPinsFragment)
248
r.Post("/bio", s.UpdateProfileBio)
···
250
})
251
252
r.Mount("/settings", s.SettingsRouter())
253
+
r.Mount("/oauth", s.OAuthRouter())
254
r.Get("/keys/{user}", s.Keys)
255
256
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
···
259
return r
260
}
261
262
+
func (s *State) OAuthRouter() http.Handler {
263
+
oauth := &oauthhandler.OAuthHandler{
264
+
Config: s.config,
265
+
Pages: s.pages,
266
+
Resolver: s.resolver,
267
+
Db: s.db,
268
+
Store: sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)),
269
+
OAuth: s.oauth,
270
+
}
271
+
272
+
return oauth.Router()
273
+
}
274
+
275
func (s *State) SettingsRouter() http.Handler {
276
settings := &settings.Settings{
277
Db: s.db,
278
+
OAuth: s.oauth,
279
Pages: s.pages,
280
Config: s.config,
281
}
+8
-4
appview/state/star.go
+8
-4
appview/state/star.go
···
15
)
16
17
func (s *State) Star(w http.ResponseWriter, r *http.Request) {
18
-
currentUser := s.auth.GetUser(r)
19
20
subject := r.URL.Query().Get("subject")
21
if subject == "" {
···
29
return
30
}
31
32
-
client, _ := s.auth.AuthorizedClient(r)
33
34
switch r.Method {
35
case http.MethodPost:
36
createdAt := time.Now().Format(time.RFC3339)
37
rkey := appview.TID()
38
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
39
Collection: tangled.FeedStarNSID,
40
Repo: currentUser.Did,
41
Rkey: rkey,
···
80
return
81
}
82
83
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
84
Collection: tangled.FeedStarNSID,
85
Repo: currentUser.Did,
86
Rkey: star.Rkey,
···
15
)
16
17
func (s *State) Star(w http.ResponseWriter, r *http.Request) {
18
+
currentUser := s.oauth.GetUser(r)
19
20
subject := r.URL.Query().Get("subject")
21
if subject == "" {
···
29
return
30
}
31
32
+
client, err := s.oauth.AuthorizedClient(r)
33
+
if err != nil {
34
+
log.Println("failed to authorize client", err)
35
+
return
36
+
}
37
38
switch r.Method {
39
case http.MethodPost:
40
createdAt := time.Now().Format(time.RFC3339)
41
rkey := appview.TID()
42
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
43
Collection: tangled.FeedStarNSID,
44
Repo: currentUser.Did,
45
Rkey: rkey,
···
84
return
85
}
86
87
+
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
88
Collection: tangled.FeedStarNSID,
89
Repo: currentUser.Did,
90
Rkey: star.Rkey,
+98
-85
appview/state/state.go
+98
-85
appview/state/state.go
···
21
"tangled.sh/tangled.sh/core/appview"
22
"tangled.sh/tangled.sh/core/appview/auth"
23
"tangled.sh/tangled.sh/core/appview/db"
24
"tangled.sh/tangled.sh/core/appview/pages"
25
"tangled.sh/tangled.sh/core/jetstream"
26
"tangled.sh/tangled.sh/core/rbac"
···
29
type State struct {
30
db *db.DB
31
auth *auth.Auth
32
enforcer *rbac.Enforcer
33
-
tidClock *syntax.TIDClock
34
pages *pages.Pages
35
resolver *appview.Resolver
36
jc *jetstream.JetstreamClient
···
38
}
39
40
func Make(config *appview.Config) (*State, error) {
41
-
d, err := db.Make(config.DbPath)
42
if err != nil {
43
return nil, err
44
}
45
46
-
auth, err := auth.Make(config.CookieSecret)
47
if err != nil {
48
return nil, err
49
}
50
51
-
enforcer, err := rbac.NewEnforcer(config.DbPath)
52
if err != nil {
53
return nil, err
54
}
···
58
pgs := pages.NewPages(config)
59
60
resolver := appview.NewResolver()
61
62
wrapper := db.DbWrapper{d}
63
jc, err := jetstream.NewJetstreamClient(
64
-
config.JetstreamEndpoint,
65
"appview",
66
[]string{
67
tangled.GraphFollowNSID,
···
86
state := &State{
87
d,
88
auth,
89
enforcer,
90
clock,
91
pgs,
···
101
return c.Next().String()
102
}
103
104
-
func (s *State) Login(w http.ResponseWriter, r *http.Request) {
105
-
ctx := r.Context()
106
107
-
switch r.Method {
108
-
case http.MethodGet:
109
-
err := s.pages.Login(w, pages.LoginParams{})
110
-
if err != nil {
111
-
log.Printf("rendering login page: %s", err)
112
-
}
113
114
-
return
115
-
case http.MethodPost:
116
-
handle := strings.TrimPrefix(r.FormValue("handle"), "@")
117
-
appPassword := r.FormValue("app_password")
118
119
-
resolved, err := s.resolver.ResolveIdent(ctx, handle)
120
-
if err != nil {
121
-
log.Println("failed to resolve handle:", err)
122
-
s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle))
123
-
return
124
-
}
125
126
-
atSession, err := s.auth.CreateInitialSession(ctx, resolved, appPassword)
127
-
if err != nil {
128
-
s.pages.Notice(w, "login-msg", "Invalid handle or password.")
129
-
return
130
-
}
131
-
sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession}
132
133
-
err = s.auth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint())
134
-
if err != nil {
135
-
s.pages.Notice(w, "login-msg", "Failed to login, try again later.")
136
-
return
137
-
}
138
139
-
log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did)
140
141
-
did := resolved.DID.String()
142
-
defaultKnot := "knot1.tangled.sh"
143
144
-
go func() {
145
-
log.Printf("adding %s to default knot", did)
146
-
err = s.enforcer.AddMember(defaultKnot, did)
147
-
if err != nil {
148
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
149
-
return
150
-
}
151
-
err = s.enforcer.E.SavePolicy()
152
-
if err != nil {
153
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
154
-
return
155
-
}
156
157
-
secret, err := db.GetRegistrationKey(s.db, defaultKnot)
158
-
if err != nil {
159
-
log.Println("failed to get registration key for knot1.tangled.sh")
160
-
return
161
-
}
162
-
signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Dev)
163
-
resp, err := signedClient.AddMember(did)
164
-
if err != nil {
165
-
log.Println("failed to add user to knot1.tangled.sh: ", err)
166
-
return
167
-
}
168
169
-
if resp.StatusCode != http.StatusNoContent {
170
-
log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode)
171
-
return
172
-
}
173
-
}()
174
175
-
s.pages.HxRedirect(w, "/")
176
-
return
177
-
}
178
-
}
179
180
func (s *State) Logout(w http.ResponseWriter, r *http.Request) {
181
-
s.auth.ClearSession(r, w)
182
w.Header().Set("HX-Redirect", "/login")
183
w.WriteHeader(http.StatusSeeOther)
184
}
185
186
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
187
-
user := s.auth.GetUser(r)
188
189
timeline, err := db.MakeTimeline(s.db)
190
if err != nil {
···
235
236
return
237
case http.MethodPost:
238
-
session, err := s.auth.Store.Get(r, appview.SessionName)
239
if err != nil || session.IsNew {
240
log.Println("unauthorized attempt to generate registration key")
241
http.Error(w, "Forbidden", http.StatusUnauthorized)
···
297
298
// create a signed request and check if a node responds to that
299
func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
300
-
user := s.auth.GetUser(r)
301
302
domain := chi.URLParam(r, "domain")
303
if domain == "" {
···
312
return
313
}
314
315
-
client, err := NewSignedClient(domain, secret, s.config.Dev)
316
if err != nil {
317
log.Println("failed to create client to ", domain)
318
}
···
421
return
422
}
423
424
-
user := s.auth.GetUser(r)
425
reg, err := db.RegistrationByDomain(s.db, domain)
426
if err != nil {
427
w.Write([]byte("failed to pull up registration info"))
···
469
// get knots registered by this user
470
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
471
// for now, this is just pubkeys
472
-
user := s.auth.GetUser(r)
473
registrations, err := db.RegistrationsByDid(s.db, user.Did)
474
if err != nil {
475
log.Println(err)
···
522
log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
523
524
// announce this relation into the firehose, store into owners' pds
525
-
client, _ := s.auth.AuthorizedClient(r)
526
-
currentUser := s.auth.GetUser(r)
527
createdAt := time.Now().Format(time.RFC3339)
528
-
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
529
Collection: tangled.KnotMemberNSID,
530
Repo: currentUser.Did,
531
Rkey: appview.TID(),
···
550
return
551
}
552
553
-
ksClient, err := NewSignedClient(domain, secret, s.config.Dev)
554
if err != nil {
555
log.Println("failed to create client to ", domain)
556
return
···
614
func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) {
615
switch r.Method {
616
case http.MethodGet:
617
-
user := s.auth.GetUser(r)
618
knots, err := s.enforcer.GetDomainsForUser(user.Did)
619
if err != nil {
620
s.pages.Notice(w, "repo", "Invalid user account.")
···
627
})
628
629
case http.MethodPost:
630
-
user := s.auth.GetUser(r)
631
632
domain := r.FormValue("domain")
633
if domain == "" {
···
671
return
672
}
673
674
-
client, err := NewSignedClient(domain, secret, s.config.Dev)
675
if err != nil {
676
s.pages.Notice(w, "repo", "Failed to connect to knot server.")
677
return
···
686
Description: description,
687
}
688
689
-
xrpcClient, _ := s.auth.AuthorizedClient(r)
690
691
createdAt := time.Now().Format(time.RFC3339)
692
-
atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{
693
Collection: tangled.RepoNSID,
694
Repo: user.Did,
695
Rkey: rkey,
···
21
"tangled.sh/tangled.sh/core/appview"
22
"tangled.sh/tangled.sh/core/appview/auth"
23
"tangled.sh/tangled.sh/core/appview/db"
24
+
"tangled.sh/tangled.sh/core/appview/oauth"
25
"tangled.sh/tangled.sh/core/appview/pages"
26
"tangled.sh/tangled.sh/core/jetstream"
27
"tangled.sh/tangled.sh/core/rbac"
···
30
type State struct {
31
db *db.DB
32
auth *auth.Auth
33
+
oauth *oauth.OAuth
34
enforcer *rbac.Enforcer
35
+
tidClock syntax.TIDClock
36
pages *pages.Pages
37
resolver *appview.Resolver
38
jc *jetstream.JetstreamClient
···
40
}
41
42
func Make(config *appview.Config) (*State, error) {
43
+
d, err := db.Make(config.Core.DbPath)
44
if err != nil {
45
return nil, err
46
}
47
48
+
auth, err := auth.Make(config.Core.CookieSecret)
49
if err != nil {
50
return nil, err
51
}
52
53
+
enforcer, err := rbac.NewEnforcer(config.Core.DbPath)
54
if err != nil {
55
return nil, err
56
}
···
60
pgs := pages.NewPages(config)
61
62
resolver := appview.NewResolver()
63
+
64
+
oauth := oauth.NewOAuth(d, config)
65
66
wrapper := db.DbWrapper{d}
67
jc, err := jetstream.NewJetstreamClient(
68
+
config.Jetstream.Endpoint,
69
"appview",
70
[]string{
71
tangled.GraphFollowNSID,
···
90
state := &State{
91
d,
92
auth,
93
+
oauth,
94
enforcer,
95
clock,
96
pgs,
···
106
return c.Next().String()
107
}
108
109
+
// func (s *State) Login(w http.ResponseWriter, r *http.Request) {
110
+
// ctx := r.Context()
111
112
+
// switch r.Method {
113
+
// case http.MethodGet:
114
+
// err := s.pages.Login(w, pages.LoginParams{})
115
+
// if err != nil {
116
+
// log.Printf("rendering login page: %s", err)
117
+
// }
118
119
+
// return
120
+
// case http.MethodPost:
121
+
// handle := strings.TrimPrefix(r.FormValue("handle"), "@")
122
+
// appPassword := r.FormValue("app_password")
123
124
+
// resolved, err := s.resolver.ResolveIdent(ctx, handle)
125
+
// if err != nil {
126
+
// log.Println("failed to resolve handle:", err)
127
+
// s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle))
128
+
// return
129
+
// }
130
131
+
// atSession, err := s.oauth.CreateInitialSession(ctx, resolved, appPassword)
132
+
// if err != nil {
133
+
// s.pages.Notice(w, "login-msg", "Invalid handle or password.")
134
+
// return
135
+
// }
136
+
// sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession}
137
138
+
// err = s.oauth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint())
139
+
// if err != nil {
140
+
// s.pages.Notice(w, "login-msg", "Failed to login, try again later.")
141
+
// return
142
+
// }
143
144
+
// log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did)
145
146
+
// did := resolved.DID.String()
147
+
// defaultKnot := "knot1.tangled.sh"
148
149
+
// go func() {
150
+
// log.Printf("adding %s to default knot", did)
151
+
// err = s.enforcer.AddMember(defaultKnot, did)
152
+
// if err != nil {
153
+
// log.Println("failed to add user to knot1.tangled.sh: ", err)
154
+
// return
155
+
// }
156
+
// err = s.enforcer.E.SavePolicy()
157
+
// if err != nil {
158
+
// log.Println("failed to add user to knot1.tangled.sh: ", err)
159
+
// return
160
+
// }
161
162
+
// secret, err := db.GetRegistrationKey(s.db, defaultKnot)
163
+
// if err != nil {
164
+
// log.Println("failed to get registration key for knot1.tangled.sh")
165
+
// return
166
+
// }
167
+
// signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Core.Dev)
168
+
// resp, err := signedClient.AddMember(did)
169
+
// if err != nil {
170
+
// log.Println("failed to add user to knot1.tangled.sh: ", err)
171
+
// return
172
+
// }
173
174
+
// if resp.StatusCode != http.StatusNoContent {
175
+
// log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode)
176
+
// return
177
+
// }
178
+
// }()
179
180
+
// s.pages.HxRedirect(w, "/")
181
+
// return
182
+
// }
183
+
// }
184
185
func (s *State) Logout(w http.ResponseWriter, r *http.Request) {
186
+
s.oauth.ClearSession(r, w)
187
w.Header().Set("HX-Redirect", "/login")
188
w.WriteHeader(http.StatusSeeOther)
189
}
190
191
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
192
+
user := s.oauth.GetUser(r)
193
194
timeline, err := db.MakeTimeline(s.db)
195
if err != nil {
···
240
241
return
242
case http.MethodPost:
243
+
session, err := s.oauth.Store.Get(r, appview.SessionName)
244
if err != nil || session.IsNew {
245
log.Println("unauthorized attempt to generate registration key")
246
http.Error(w, "Forbidden", http.StatusUnauthorized)
···
302
303
// create a signed request and check if a node responds to that
304
func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
305
+
user := s.oauth.GetUser(r)
306
307
domain := chi.URLParam(r, "domain")
308
if domain == "" {
···
317
return
318
}
319
320
+
client, err := NewSignedClient(domain, secret, s.config.Core.Dev)
321
if err != nil {
322
log.Println("failed to create client to ", domain)
323
}
···
426
return
427
}
428
429
+
user := s.oauth.GetUser(r)
430
reg, err := db.RegistrationByDomain(s.db, domain)
431
if err != nil {
432
w.Write([]byte("failed to pull up registration info"))
···
474
// get knots registered by this user
475
func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
476
// for now, this is just pubkeys
477
+
user := s.oauth.GetUser(r)
478
registrations, err := db.RegistrationsByDid(s.db, user.Did)
479
if err != nil {
480
log.Println(err)
···
527
log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
528
529
// announce this relation into the firehose, store into owners' pds
530
+
client, err := s.oauth.AuthorizedClient(r)
531
+
if err != nil {
532
+
http.Error(w, "failed to authorize client", http.StatusInternalServerError)
533
+
return
534
+
}
535
+
currentUser := s.oauth.GetUser(r)
536
createdAt := time.Now().Format(time.RFC3339)
537
+
resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
538
Collection: tangled.KnotMemberNSID,
539
Repo: currentUser.Did,
540
Rkey: appview.TID(),
···
559
return
560
}
561
562
+
ksClient, err := NewSignedClient(domain, secret, s.config.Core.Dev)
563
if err != nil {
564
log.Println("failed to create client to ", domain)
565
return
···
623
func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) {
624
switch r.Method {
625
case http.MethodGet:
626
+
user := s.oauth.GetUser(r)
627
knots, err := s.enforcer.GetDomainsForUser(user.Did)
628
if err != nil {
629
s.pages.Notice(w, "repo", "Invalid user account.")
···
636
})
637
638
case http.MethodPost:
639
+
user := s.oauth.GetUser(r)
640
641
domain := r.FormValue("domain")
642
if domain == "" {
···
680
return
681
}
682
683
+
client, err := NewSignedClient(domain, secret, s.config.Core.Dev)
684
if err != nil {
685
s.pages.Notice(w, "repo", "Failed to connect to knot server.")
686
return
···
695
Description: description,
696
}
697
698
+
xrpcClient, err := s.oauth.AuthorizedClient(r)
699
+
if err != nil {
700
+
s.pages.Notice(w, "repo", "Failed to write record to PDS.")
701
+
return
702
+
}
703
704
createdAt := time.Now().Format(time.RFC3339)
705
+
atresp, err := xrpcClient.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
706
Collection: tangled.RepoNSID,
707
Repo: user.Did,
708
Rkey: rkey,
+1
-1
appview/tid.go
+1
-1
appview/tid.go
+2
-2
cmd/appview/main.go
+2
-2
cmd/appview/main.go