+2
.gitignore
+2
.gitignore
+24
appview/oauth/client/oauth_client.go
+24
appview/oauth/client/oauth_client.go
···
···
1
+
package client
2
+
3
+
import (
4
+
oauth "github.com/haileyok/atproto-oauth-golang"
5
+
"github.com/haileyok/atproto-oauth-golang/helpers"
6
+
)
7
+
8
+
type OAuthClient struct {
9
+
*oauth.Client
10
+
}
11
+
12
+
func NewClient(clientId, clientJwk, redirectUri string) (*OAuthClient, error) {
13
+
k, err := helpers.ParseJWKFromBytes([]byte(clientJwk))
14
+
if err != nil {
15
+
return nil, err
16
+
}
17
+
18
+
cli, err := oauth.NewClient(oauth.ClientArgs{
19
+
ClientId: clientId,
20
+
ClientJwk: k,
21
+
RedirectUri: redirectUri,
22
+
})
23
+
return &OAuthClient{cli}, err
24
+
}
+18
-12
go.mod
+18
-12
go.mod
···
1
module tangled.sh/tangled.sh/core
2
3
-
go 1.23.0
4
5
-
toolchain go1.23.6
6
7
require (
8
github.com/Blank-Xu/sql-adapter v1.1.1
9
github.com/alecthomas/chroma/v2 v2.15.0
10
github.com/bluekeyes/go-gitdiff v0.8.1
11
-
github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20
12
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1
13
github.com/casbin/casbin/v2 v2.103.0
14
github.com/cyphar/filepath-securejoin v0.4.1
···
19
github.com/go-git/go-git/v5 v5.14.0
20
github.com/google/uuid v1.6.0
21
github.com/gorilla/sessions v1.4.0
22
github.com/ipfs/go-cid v0.5.0
23
github.com/mattn/go-sqlite3 v1.14.24
24
github.com/microcosm-cc/bluemonday v1.0.27
25
github.com/resend/resend-go/v2 v2.15.0
···
41
github.com/casbin/govaluate v1.3.0 // indirect
42
github.com/cespare/xxhash/v2 v2.3.0 // indirect
43
github.com/cloudflare/circl v1.6.0 // indirect
44
-
github.com/davecgh/go-spew v1.1.1 // indirect
45
github.com/dlclark/regexp2 v1.11.5 // indirect
46
github.com/emirpasic/gods v1.18.1 // indirect
47
github.com/felixge/httpsnoop v1.0.4 // indirect
48
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
49
github.com/go-git/go-billy/v5 v5.6.2 // indirect
50
-
github.com/go-logr/logr v1.4.1 // indirect
51
github.com/go-logr/stdr v1.2.2 // indirect
52
github.com/goccy/go-json v0.10.2 // indirect
53
github.com/gogo/protobuf v1.3.2 // indirect
54
github.com/gorilla/css v1.0.1 // indirect
55
github.com/gorilla/securecookie v1.1.2 // indirect
56
github.com/gorilla/websocket v1.5.1 // indirect
···
75
github.com/kevinburke/ssh_config v1.2.0 // indirect
76
github.com/klauspost/compress v1.17.9 // indirect
77
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
78
github.com/mattn/go-isatty v0.0.20 // indirect
79
github.com/minio/sha256-simd v1.0.1 // indirect
80
github.com/mr-tron/base58 v1.2.0 // indirect
···
86
github.com/opentracing/opentracing-go v1.2.0 // indirect
87
github.com/pjbgf/sha1cd v0.3.2 // indirect
88
github.com/pkg/errors v0.9.1 // indirect
89
-
github.com/pmezard/go-difflib v1.0.0 // indirect
90
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
91
github.com/prometheus/client_golang v1.19.1 // indirect
92
github.com/prometheus/client_model v0.6.1 // indirect
93
github.com/prometheus/common v0.54.0 // indirect
94
github.com/prometheus/procfs v0.15.1 // indirect
95
github.com/sergi/go-diff v1.3.1 // indirect
96
github.com/skeema/knownhosts v1.3.1 // indirect
97
github.com/spaolacci/murmur3 v1.1.0 // indirect
98
-
github.com/stretchr/testify v1.10.0 // indirect
99
github.com/xanzy/ssh-agent v0.3.3 // indirect
100
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
101
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
102
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
103
-
go.opentelemetry.io/otel v1.21.0 // indirect
104
-
go.opentelemetry.io/otel/metric v1.21.0 // indirect
105
-
go.opentelemetry.io/otel/trace v1.21.0 // indirect
106
go.uber.org/atomic v1.11.0 // indirect
107
go.uber.org/multierr v1.11.0 // indirect
108
go.uber.org/zap v1.26.0 // indirect
109
golang.org/x/crypto v0.37.0 // indirect
110
golang.org/x/net v0.39.0 // indirect
111
golang.org/x/sys v0.32.0 // indirect
112
-
golang.org/x/time v0.5.0 // indirect
113
google.golang.org/protobuf v1.34.2 // indirect
114
gopkg.in/warnings.v0 v0.1.2 // indirect
115
-
gopkg.in/yaml.v3 v3.0.1 // indirect
116
lukechampine.com/blake3 v1.2.1 // indirect
117
)
118
···
1
module tangled.sh/tangled.sh/core
2
3
+
go 1.24.0
4
5
+
toolchain go1.24.3
6
7
require (
8
github.com/Blank-Xu/sql-adapter v1.1.1
9
github.com/alecthomas/chroma/v2 v2.15.0
10
github.com/bluekeyes/go-gitdiff v0.8.1
11
+
github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188
12
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1
13
github.com/casbin/casbin/v2 v2.103.0
14
github.com/cyphar/filepath-securejoin v0.4.1
···
19
github.com/go-git/go-git/v5 v5.14.0
20
github.com/google/uuid v1.6.0
21
github.com/gorilla/sessions v1.4.0
22
+
github.com/haileyok/atproto-oauth-golang v0.0.2
23
github.com/ipfs/go-cid v0.5.0
24
+
github.com/lestrrat-go/jwx/v2 v2.0.12
25
github.com/mattn/go-sqlite3 v1.14.24
26
github.com/microcosm-cc/bluemonday v1.0.27
27
github.com/resend/resend-go/v2 v2.15.0
···
43
github.com/casbin/govaluate v1.3.0 // indirect
44
github.com/cespare/xxhash/v2 v2.3.0 // indirect
45
github.com/cloudflare/circl v1.6.0 // indirect
46
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
47
github.com/dlclark/regexp2 v1.11.5 // indirect
48
github.com/emirpasic/gods v1.18.1 // indirect
49
github.com/felixge/httpsnoop v1.0.4 // indirect
50
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
51
github.com/go-git/go-billy/v5 v5.6.2 // indirect
52
+
github.com/go-logr/logr v1.4.2 // indirect
53
github.com/go-logr/stdr v1.2.2 // indirect
54
github.com/goccy/go-json v0.10.2 // indirect
55
github.com/gogo/protobuf v1.3.2 // indirect
56
+
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
57
github.com/gorilla/css v1.0.1 // indirect
58
github.com/gorilla/securecookie v1.1.2 // indirect
59
github.com/gorilla/websocket v1.5.1 // indirect
···
78
github.com/kevinburke/ssh_config v1.2.0 // indirect
79
github.com/klauspost/compress v1.17.9 // indirect
80
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
81
+
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
82
+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
83
+
github.com/lestrrat-go/httprc v1.0.4 // indirect
84
+
github.com/lestrrat-go/iter v1.0.2 // indirect
85
+
github.com/lestrrat-go/option v1.0.1 // indirect
86
github.com/mattn/go-isatty v0.0.20 // indirect
87
github.com/minio/sha256-simd v1.0.1 // indirect
88
github.com/mr-tron/base58 v1.2.0 // indirect
···
94
github.com/opentracing/opentracing-go v1.2.0 // indirect
95
github.com/pjbgf/sha1cd v0.3.2 // indirect
96
github.com/pkg/errors v0.9.1 // indirect
97
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
98
github.com/prometheus/client_golang v1.19.1 // indirect
99
github.com/prometheus/client_model v0.6.1 // indirect
100
github.com/prometheus/common v0.54.0 // indirect
101
github.com/prometheus/procfs v0.15.1 // indirect
102
+
github.com/segmentio/asm v1.2.0 // indirect
103
github.com/sergi/go-diff v1.3.1 // indirect
104
github.com/skeema/knownhosts v1.3.1 // indirect
105
github.com/spaolacci/murmur3 v1.1.0 // indirect
106
github.com/xanzy/ssh-agent v0.3.3 // indirect
107
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
108
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
109
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
110
+
go.opentelemetry.io/otel v1.29.0 // indirect
111
+
go.opentelemetry.io/otel/metric v1.29.0 // indirect
112
+
go.opentelemetry.io/otel/trace v1.29.0 // indirect
113
go.uber.org/atomic v1.11.0 // indirect
114
go.uber.org/multierr v1.11.0 // indirect
115
go.uber.org/zap v1.26.0 // indirect
116
golang.org/x/crypto v0.37.0 // indirect
117
golang.org/x/net v0.39.0 // indirect
118
golang.org/x/sys v0.32.0 // indirect
119
+
golang.org/x/time v0.8.0 // indirect
120
google.golang.org/protobuf v1.34.2 // indirect
121
gopkg.in/warnings.v0 v0.1.2 // indirect
122
lukechampine.com/blake3 v1.2.1 // indirect
123
)
124
+61
-16
go.sum
+61
-16
go.sum
···
26
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
27
github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI=
28
github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
29
-
github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20 h1:yHusfYYi8odoCcsI6AurU+dRWb7itHAQNwt3/Rl9Vfs=
30
-
github.com/bluesky-social/indigo v0.0.0-20250123072624-9e3b84fdbb20/go.mod h1:Qp4YqWf+AQ3TwQCxV5Ls8O2tXE55zVTGVs3zTmn7BOg=
31
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA=
32
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4=
33
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
···
52
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
53
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
54
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
-
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
58
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
59
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
···
82
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
83
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
84
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
85
-
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
86
-
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
87
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
88
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
89
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
···
91
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
92
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
93
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
94
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
95
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
96
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
···
111
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
112
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
113
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
114
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
115
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
116
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
···
159
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
160
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
161
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
162
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
163
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
164
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
···
177
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
178
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
179
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
180
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
181
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
182
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
···
212
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
213
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
214
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
215
-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
216
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
217
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
218
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
219
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
···
227
github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE=
228
github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
229
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
230
-
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
231
-
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
232
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
233
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
234
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
235
github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog=
···
246
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
247
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
248
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
249
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
250
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
251
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
252
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
253
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
254
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
255
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
···
270
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
271
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
272
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
273
-
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
274
-
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
275
-
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
276
-
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
277
-
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
278
-
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
279
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
280
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
281
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
···
303
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
304
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
305
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
306
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
307
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
308
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
···
314
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
315
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
316
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
317
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
318
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
319
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
···
327
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
328
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
329
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
330
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
331
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
332
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
334
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
335
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
336
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
337
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
338
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
339
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
···
348
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
349
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
350
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
351
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
352
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
353
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
···
357
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
358
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
359
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
360
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
361
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
362
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
···
364
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
365
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
366
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
367
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
368
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
369
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
···
372
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
373
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
374
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
375
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
376
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
377
-
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
378
-
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
379
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
380
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
381
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
···
389
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
390
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
391
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
392
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
393
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
394
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
···
26
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
27
github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI=
28
github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE=
29
+
github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk=
30
+
github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA=
31
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA=
32
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1/go.mod h1:WiYEeyJSdUwqoaZ71KJSpTblemUCpwJfh5oVXplK6T4=
33
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
···
52
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
53
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
54
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56
+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
57
+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
58
+
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
59
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
60
+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
61
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
62
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
63
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
···
86
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
87
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
88
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
89
+
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
90
+
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
91
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
92
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
93
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
···
95
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
96
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
97
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
98
+
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
99
+
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
100
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
101
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
102
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
···
117
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
118
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
119
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
120
+
github.com/haileyok/atproto-oauth-golang v0.0.2 h1:61KPkLB615LQXR2f5x1v3sf6vPe6dOXqNpTYCgZ0Fz8=
121
+
github.com/haileyok/atproto-oauth-golang v0.0.2/go.mod h1:jcZ4GCjo5I5RuE/RsAXg1/b6udw7R4W+2rb/cGyTDK8=
122
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
123
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
124
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
···
167
github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o=
168
github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
169
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
170
+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
171
+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
172
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
173
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
174
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
···
187
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
188
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
189
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
190
+
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
191
+
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
192
+
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
193
+
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
194
+
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
195
+
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
196
+
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
197
+
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
198
+
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
199
+
github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA=
200
+
github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ=
201
+
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
202
+
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
203
+
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
204
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
205
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
206
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
···
236
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
237
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
238
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
239
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
240
+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
241
+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
242
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
243
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
244
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
···
252
github.com/resend/resend-go/v2 v2.15.0 h1:B6oMEPf8IEQwn2Ovx/9yymkESLDSeNfLFaNMw+mzHhE=
253
github.com/resend/resend-go/v2 v2.15.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
254
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
255
+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
256
+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
257
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
258
+
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
259
+
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
260
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
261
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
262
github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog=
···
273
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
274
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
275
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
276
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
277
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
278
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
279
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
280
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
281
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
282
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
283
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
284
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
285
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
286
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
287
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
288
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
···
303
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
304
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
305
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
306
+
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
307
+
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
308
+
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
309
+
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
310
+
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
311
+
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
312
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
313
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
314
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
···
336
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
337
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
338
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
339
+
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
340
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
341
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
342
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
···
348
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
349
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
350
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
351
+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
352
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
353
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
354
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
···
362
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
363
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
364
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
365
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
366
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
367
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
368
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
370
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
371
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
372
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
373
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
374
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
375
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
376
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
···
385
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
386
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
387
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
388
+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
389
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
390
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
391
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
···
395
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
396
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
397
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
398
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
399
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
400
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
401
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
402
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
···
404
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
405
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
406
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
407
+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
408
+
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
409
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
410
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
411
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
···
414
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
415
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
416
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
417
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
418
+
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
419
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
420
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
421
+
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
422
+
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
423
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
424
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
425
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
···
433
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
434
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
435
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
436
+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
437
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
438
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
439
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+80
appview/xrpcclient/xrpc.go
+80
appview/xrpcclient/xrpc.go
···
···
1
+
package xrpcclient
2
+
3
+
import (
4
+
"bytes"
5
+
"context"
6
+
"io"
7
+
8
+
"github.com/bluesky-social/indigo/api/atproto"
9
+
"github.com/bluesky-social/indigo/xrpc"
10
+
oauth "github.com/haileyok/atproto-oauth-golang"
11
+
)
12
+
13
+
type Client struct {
14
+
*oauth.XrpcClient
15
+
authArgs *oauth.XrpcAuthedRequestArgs
16
+
}
17
+
18
+
func NewClient(client *oauth.XrpcClient, authArgs *oauth.XrpcAuthedRequestArgs) *Client {
19
+
return &Client{
20
+
XrpcClient: client,
21
+
authArgs: authArgs,
22
+
}
23
+
}
24
+
25
+
func (c *Client) RepoPutRecord(ctx context.Context, input *atproto.RepoPutRecord_Input) (*atproto.RepoPutRecord_Output, error) {
26
+
var out atproto.RepoPutRecord_Output
27
+
if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.putRecord", nil, input, &out); err != nil {
28
+
return nil, err
29
+
}
30
+
31
+
return &out, nil
32
+
}
33
+
34
+
func (c *Client) RepoGetRecord(ctx context.Context, cid string, collection string, repo string, rkey string) (*atproto.RepoGetRecord_Output, error) {
35
+
var out atproto.RepoGetRecord_Output
36
+
37
+
params := map[string]interface{}{
38
+
"cid": cid,
39
+
"collection": collection,
40
+
"repo": repo,
41
+
"rkey": rkey,
42
+
}
43
+
if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.repo.getRecord", params, nil, &out); err != nil {
44
+
return nil, err
45
+
}
46
+
47
+
return &out, nil
48
+
}
49
+
50
+
func (c *Client) RepoUploadBlob(ctx context.Context, input io.Reader) (*atproto.RepoUploadBlob_Output, error) {
51
+
var out atproto.RepoUploadBlob_Output
52
+
if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "*/*", "com.atproto.repo.uploadBlob", nil, input, &out); err != nil {
53
+
return nil, err
54
+
}
55
+
56
+
return &out, nil
57
+
}
58
+
59
+
func (c *Client) SyncGetBlob(ctx context.Context, cid string, did string) ([]byte, error) {
60
+
buf := new(bytes.Buffer)
61
+
62
+
params := map[string]interface{}{
63
+
"cid": cid,
64
+
"did": did,
65
+
}
66
+
if err := c.Do(ctx, c.authArgs, xrpc.Query, "", "com.atproto.sync.getBlob", params, nil, buf); err != nil {
67
+
return nil, err
68
+
}
69
+
70
+
return buf.Bytes(), nil
71
+
}
72
+
73
+
func (c *Client) RepoDeleteRecord(ctx context.Context, input *atproto.RepoDeleteRecord_Input) (*atproto.RepoDeleteRecord_Output, error) {
74
+
var out atproto.RepoDeleteRecord_Output
75
+
if err := c.Do(ctx, c.authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.deleteRecord", nil, input, &out); err != nil {
76
+
return nil, err
77
+
}
78
+
79
+
return &out, nil
80
+
}
+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)
95
})
96
}
···
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)
42
})
43
}
+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}
+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,
+109
-96
appview/state/state.go
+109
-96
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
}
···
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
}
···
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
+12
-2
appview/oauth/handler/handler.go
+12
-2
appview/oauth/handler/handler.go
···
61
"token_endpoint_auth_signing_alg": "ES256",
62
}
63
64
-
fmt.Println("clientMetadata", metadata)
65
-
66
w.Header().Set("Content-Type", "application/json")
67
w.WriteHeader(http.StatusOK)
68
json.NewEncoder(w).Encode(metadata)
···
246
http.Redirect(w, r, "/", http.StatusFound)
247
}
248
249
func pubKeyFromJwk(jwks string) (jwk.Key, error) {
250
k, err := helpers.ParseJWKFromBytes([]byte(jwks))
251
if err != nil {
···
61
"token_endpoint_auth_signing_alg": "ES256",
62
}
63
64
w.Header().Set("Content-Type", "application/json")
65
w.WriteHeader(http.StatusOK)
66
json.NewEncoder(w).Encode(metadata)
···
244
http.Redirect(w, r, "/", http.StatusFound)
245
}
246
247
+
func (o *OAuthHandler) logout(w http.ResponseWriter, r *http.Request) {
248
+
err := o.OAuth.ClearSession(r, w)
249
+
if err != nil {
250
+
log.Println("failed to clear session:", err)
251
+
http.Redirect(w, r, "/", http.StatusFound)
252
+
return
253
+
}
254
+
255
+
log.Println("session cleared successfully")
256
+
http.Redirect(w, r, "/", http.StatusFound)
257
+
}
258
+
259
func pubKeyFromJwk(jwks string) (jwk.Key, error) {
260
k, err := helpers.ParseJWKFromBytes([]byte(jwks))
261
if err != nil {