appview: oauth: setup handlers and service #90

closed
opened by oppi.li targeting master from oauth
+2
.gitignore
··· 11 ./avatar/node_modules/* 12 patches 13 *.qcow2
··· 11 ./avatar/node_modules/* 12 patches 13 *.qcow2 14 + .DS_Store 15 + .env
+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
··· 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
··· 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
···
··· 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 [build] 2 cmd = "tailwindcss -i input.css -o ./appview/pages/static/tw.css && go build -o .bin/app ./cmd/appview/main.go" 3 - bin = ".bin/app" 4 root = "." 5 6 exclude_regex = [".*_templ.go"]
··· 1 [build] 2 cmd = "tailwindcss -i input.css -o ./appview/pages/static/tw.css && go build -o .bin/app ./cmd/appview/main.go" 3 + bin = ";set -o allexport && source .env && set +o allexport; .bin/app" 4 root = "." 5 6 exclude_regex = [".*_templ.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
··· 9 SessionRefreshJwt = "refreshJwt" 10 SessionExpiry = "expiry" 11 SessionAuthenticated = "authenticated" 12 )
··· 9 SessionRefreshJwt = "refreshJwt" 10 SessionExpiry = "expiry" 11 SessionAuthenticated = "authenticated" 12 + 13 + SessionDpopPrivateJwk = "dpopPrivateJwk" 14 + SessionDpopAuthServerNonce = "dpopAuthServerNonce" 15 )
+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 migrations ( 292 id integer primary key autoincrement, 293 name text unique
··· 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
···
··· 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 "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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 4 "github.com/bluesky-social/indigo/atproto/syntax" 5 ) 6 7 - var c *syntax.TIDClock = syntax.NewTIDClock(0) 8 9 func TID() string { 10 return c.Next().String()
··· 4 "github.com/bluesky-social/indigo/atproto/syntax" 5 ) 6 7 + var c syntax.TIDClock = syntax.NewTIDClock(0) 8 9 func TID() string { 10 return c.Next().String()
+2 -2
cmd/appview/main.go
··· 26 log.Fatal(err) 27 } 28 29 - log.Println("starting server on", c.ListenAddr) 30 - log.Println(http.ListenAndServe(c.ListenAddr, state.Router())) 31 }
··· 26 log.Fatal(err) 27 } 28 29 + log.Println("starting server on", c.Core.ListenAddr) 30 + log.Println(http.ListenAndServe(c.Core.ListenAddr, state.Router())) 31 }
+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 {