diff --git a/Makefile b/Makefile index fc69ca2cb..5a1a267df 100644 --- a/Makefile +++ b/Makefile @@ -106,18 +106,40 @@ js-lexicons: && sed -i.bak "s/'\.\.\/\.\.\/\.\.\/com/'@atproto\/api\/src\/client\/types\/com/" $$(find ./js/app/lexicons/types/place/stream -type f) \ && sed -i.bak 's/AppBskyGraphBlock\.Main/AppBskyGraphBlock\.Record/' $$(find ./js/app/lexicons/types/place/stream -type f) \ && sed -i.bak 's/PlaceStreamChatProfile\.Main/PlaceStreamChatProfile\.Record/' $$(find ./js/app/lexicons/types/place/stream -type f) \ + && sed -i.bak "s/import\ \*\ as\ AppBskyFeedDefs\ from\ '.\/defs'/import \{ AppBskyFeedDefs } from '@atproto\/api'/" $$(find ./js/app/lexicons/types -type f) \ && rm -rf ./js/app/lexicons/types/place/stream/*.bak .PHONY: lexgen lexgen: + $(MAKE) lexgen-types + $(MAKE) lexgen-server + +.PHONY: lexgen-types +lexgen-types: go run github.com/bluesky-social/indigo/cmd/lexgen --package streamplace \ --types-import place.stream:stream.place/streamplace/pkg/streamplace \ -outdir ./pkg/streamplace \ --prefix place.stream \ - --build-file util/lexgen-build.json \ + --build-file util/lexgen-types.json \ lexicons/place/stream \ ../atproto/lexicons +.PHONY: lexgen-server +lexgen-server: + mkdir -p ./pkg/spxrpc + go run github.com/bluesky-social/indigo/cmd/lexgen --package spxrpc \ + --gen-server \ + --types-import place.stream:stream.place/streamplace/pkg/streamplace \ + --types-import app.bsky:github.com/bluesky-social/indigo/api/bsky \ + --types-import com.atproto:github.com/bluesky-social/indigo/api/atproto \ + --types-import chat.bsky:github.com/bluesky-social/indigo/api/chat \ + --types-import tools.ozone:github.com/bluesky-social/indigo/api/ozone \ + -outdir ./pkg/spxrpc \ + --prefix place.stream \ + --build-file util/lexgen-server.json \ + lexicons/place/stream \ + lexicons/app/bsky + .PHONY: test test: meson test -C $(BUILDDIR) go-tests diff --git a/go.mod b/go.mod index 9a37c6faf..ec4f8c7f0 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/ThalesGroup/crypto11 v0.0.0-00010101000000-000000000000 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/bluesky-social/indigo v0.0.0-20250202033855-fd270fbccf0c + github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 github.com/decred/dcrd/dcrec/secp256k1 v1.0.4 github.com/dunglas/httpsfv v1.0.2 github.com/ethereum/go-ethereum v1.14.7 @@ -31,6 +31,8 @@ require ( github.com/ipfs/go-ipfs-blockstore v1.3.1 github.com/johncgriffin/overflow v0.0.0-20211019200055-46fa312c352c github.com/julienschmidt/httprouter v1.3.0 + github.com/labstack/echo/v4 v4.13.3 + github.com/lestrrat-go/jwx/v2 v2.0.12 github.com/livepeer/lpms v0.0.0-20240812093642-b5181eb92cb2 github.com/lmittmann/tint v1.0.4 github.com/mr-tron/base58 v1.2.0 @@ -60,7 +62,7 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/api v0.189.0 gorm.io/datatypes v1.2.4 - gorm.io/driver/sqlite v1.5.5 + gorm.io/driver/sqlite v1.5.7 ) require ( @@ -72,9 +74,9 @@ require ( ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -127,6 +129,7 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gocql/gocql v0.0.0-00010101000000-000000000000 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect @@ -178,7 +181,14 @@ require ( github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/livepeer/m3u8 v0.11.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-pointer v0.0.1 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect @@ -216,6 +226,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect @@ -225,6 +236,8 @@ require ( github.com/supranational/blst v0.3.11 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/urfave/cli/v2 v2.27.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vektah/gqlparser/v2 v2.5.22 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/wlynxg/anet v0.0.5 // indirect @@ -248,7 +261,7 @@ require ( golang.org/x/mod v0.21.0 // indirect golang.org/x/oauth2 v0.26.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.8.0 // indirect google.golang.org/appengine/v2 v2.0.2 // indirect google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect diff --git a/go.sum b/go.sum index 648f141b9..c3ad5c1f6 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYE github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bluesky-social/indigo v0.0.0-20250202033855-fd270fbccf0c h1:wbp2fKVXy5XtM1FfSagSFtl1vFm4CgA7vE3KEzX/XyQ= -github.com/bluesky-social/indigo v0.0.0-20250202033855-fd270fbccf0c/go.mod h1:Qp4YqWf+AQ3TwQCxV5Ls8O2tXE55zVTGVs3zTmn7BOg= +github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188 h1:1sQaG37xk08/rpmdhrmMkfQWF9kZbnfHm9Zav3bbSMk= +github.com/bluesky-social/indigo v0.0.0-20250301025210-a4e0cc37e188/go.mod h1:NVBwZvbBSa93kfyweAmKwOLYawdVHdwZ9s+GZtBBVLA= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= @@ -126,8 +126,9 @@ github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TI github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= @@ -198,6 +199,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -409,8 +412,26 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= +github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= +github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= @@ -434,6 +455,8 @@ github.com/livepeer/m3u8 v0.11.1 h1:VkUJzfNTyjy9mqsgp5JPvouwna8wGZMvd/gAfT5FinU= github.com/livepeer/m3u8 v0.11.1/go.mod h1:IUqAtwWPAG2CblfQa4SVzTQoDcEMPyfNOaBSxqHMS04= github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -536,8 +559,9 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= @@ -566,6 +590,8 @@ github.com/samber/slog-http v1.4.0 h1:s2hSzMlQBFDIPTUBDm6G+SGfv7F4xR9q8edjxtY6ah github.com/samber/slog-http v1.4.0/go.mod h1:n6h4x2ZBeTgLqMKf95EuNlU6mcJF1b/RVLxo1od5+V0= github.com/scylladb/gocql v1.14.4 h1:MhevwCfyAraQ6RvZYFO3pF4Lt0YhvQlfg8Eo2HEqVQA= github.com/scylladb/gocql v1.14.4/go.mod h1:ZLEJ0EVE5JhmtxIW2stgHq/v1P4fWap0qyyXSKyV8K0= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= @@ -594,6 +620,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -615,6 +642,10 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vektah/gqlparser/v2 v2.5.22 h1:yaaeJ0fu+nv1vUMW0Hl+aS1eiv1vMfapBNjpffAda1I= github.com/vektah/gqlparser/v2 v2.5.22/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM= github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= @@ -691,6 +722,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -728,6 +760,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -755,6 +788,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -763,6 +797,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -770,6 +806,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -779,10 +817,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -865,8 +905,8 @@ gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= -gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= -gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/lexicons/app/bsky/feed/getFeedSkeleton.json b/lexicons/app/bsky/feed/getFeedSkeleton.json new file mode 100644 index 000000000..2bcaa13d4 --- /dev/null +++ b/lexicons/app/bsky/feed/getFeedSkeleton.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "app.bsky.feed.getFeedSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.", + "parameters": { + "type": "params", + "required": ["feed"], + "properties": { + "feed": { + "type": "string", + "format": "at-uri", + "description": "Reference to feed generator record describing the specific feed being requested." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feed"], + "properties": { + "cursor": { "type": "string" }, + "feed": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#skeletonFeedPost" + } + } + } + } + }, + "errors": [{ "name": "UnknownFeed" }] + } + } +} diff --git a/pkg/api/api.go b/pkg/api/api.go index c1d82b15d..fd84dbbd8 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -38,6 +38,7 @@ import ( "stream.place/streamplace/pkg/notifications" "stream.place/streamplace/pkg/renditions" "stream.place/streamplace/pkg/spmetrics" + "stream.place/streamplace/pkg/spxrpc" "stream.place/streamplace/pkg/streamplace" ) @@ -107,6 +108,10 @@ func (fs AppHostingFS) Open(name string) (http.File, error) { // api/playback/iame.li/hls/source/000000000000.ts func (a *StreamplaceAPI) Handler(ctx context.Context) (http.Handler, error) { + xrpc, err := spxrpc.NewServer(a.CLI, a.Model) + if err != nil { + return nil, err + } router := httprouter.New() apiRouter := httprouter.New() apiRouter.HandlerFunc("POST", "/api/notification", a.HandleNotification(ctx)) @@ -138,21 +143,22 @@ func (a *StreamplaceAPI) Handler(ctx context.Context) (http.Handler, error) { apiRouter.GET("/api/segment/recent", a.HandleRecentSegments(ctx)) apiRouter.GET("/api/segment/recent/:repoDID", a.HandleUserRecentSegments(ctx)) apiRouter.GET("/api/bluesky/resolve/:handle", a.HandleBlueskyResolve(ctx)) - apiRouter.GET("/api/atproto-oauth/:platform", a.HandleATProtoOAuth(ctx)) + for _, platform := range atproto.AllowedPlatforms { + apiRouter.GET(fmt.Sprintf("/api/atproto-oauth/%s", platform), a.HandleATProtoOAuth(ctx, platform)) + } apiRouter.GET("/api/live-users", a.HandleLiveUsers(ctx)) apiRouter.GET("/api/view-count/:user", a.HandleViewCount(ctx)) - apiRouter.GET("/xrpc/app.bsky.feed.getFeedSkeleton", a.HandleXRPCAppBskyFeedGetFeedSkeleton(ctx)) apiRouter.NotFound = a.HandleAPI404(ctx) router.Handler("GET", "/api/*resource", apiRouter) router.Handler("POST", "/api/*resource", apiRouter) router.Handler("PUT", "/api/*resource", apiRouter) router.Handler("PATCH", "/api/*resource", apiRouter) router.Handler("DELETE", "/api/*resource", apiRouter) - router.Handler("GET", "/xrpc/*resource", apiRouter) - router.Handler("POST", "/xrpc/*resource", apiRouter) - router.Handler("PUT", "/xrpc/*resource", apiRouter) - router.Handler("PATCH", "/xrpc/*resource", apiRouter) - router.Handler("DELETE", "/xrpc/*resource", apiRouter) + router.Handler("GET", "/xrpc/*resource", xrpc) + router.Handler("POST", "/xrpc/*resource", xrpc) + router.Handler("PUT", "/xrpc/*resource", xrpc) + router.Handler("PATCH", "/xrpc/*resource", xrpc) + router.Handler("DELETE", "/xrpc/*resource", xrpc) router.GET("/.well-known/did.json", a.HandleDidJson(ctx)) router.GET("/dl/*params", a.HandleAppDownload(ctx)) router.POST("/", a.HandleWebRTCIngest(ctx)) @@ -565,13 +571,12 @@ func (a *StreamplaceAPI) HandleBlueskyResolve(ctx context.Context) httprouter.Ha } } -func (a *StreamplaceAPI) HandleATProtoOAuth(ctx context.Context) httprouter.Handle { +func (a *StreamplaceAPI) HandleATProtoOAuth(ctx context.Context, platform string) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { host, _, err := net.SplitHostPort(req.Host) if err != nil { host = req.Host } - platform := params.ByName("platform") if !slices.Contains(atproto.AllowedPlatforms, platform) { apierrors.WriteHTTPBadRequest(w, "unsupported platform", nil) return diff --git a/pkg/api/feeds.go b/pkg/api/feeds.go index e951bc07f..f9909f82d 100644 --- a/pkg/api/feeds.go +++ b/pkg/api/feeds.go @@ -5,15 +5,9 @@ import ( "encoding/json" "fmt" "net/http" - "regexp" - "strconv" - "strings" - "github.com/bluesky-social/indigo/api/bsky" "github.com/julienschmidt/httprouter" - "stream.place/streamplace/pkg/errors" "stream.place/streamplace/pkg/log" - "stream.place/streamplace/pkg/model" ) func (a *StreamplaceAPI) HandleDidJson(ctx context.Context) httprouter.Handle { @@ -42,111 +36,3 @@ func (a *StreamplaceAPI) HandleDidJson(ctx context.Context) httprouter.Handle { w.Write(bs) } } - -var FeedSkeletonRE = regexp.MustCompile(`^at://did:(web|plc):([a-z0-9\.\-]+)/app.bsky.feed.generator/([a-z0-9\.\-]+)$`) - -func parseFeedSkeleton(did string) (string, string, error) { - matches := FeedSkeletonRE.FindStringSubmatch(did) - if len(matches) != 4 { - return "", "", fmt.Errorf("invalid feed parameter: %s", did) - } - return fmt.Sprintf("did:%s:%s", matches[1], matches[2]), matches[3], nil -} - -const FEED_LIVE_STREAMS = "live-streams" -const FEED_ALL_STREAMS = "all-streams" - -func (a *StreamplaceAPI) HandleXRPCAppBskyFeedGetFeedSkeleton(ctx context.Context) httprouter.Handle { - return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { - w.WriteHeader(200) - w.Header().Set("Content-Type", "application/json") - - feed := req.URL.Query().Get("feed") - if feed == "" { - errors.WriteHTTPBadRequest(w, "feed is required", nil) - return - } - _, name, err := parseFeedSkeleton(feed) - if err != nil { - errors.WriteHTTPBadRequest(w, "invalid feed parameter", err) - return - } - limit := 50 - limitStr := req.URL.Query().Get("limit") - if limitStr != "" { - limit, err = strconv.Atoi(limitStr) - if err != nil { - errors.WriteHTTPBadRequest(w, "invalid limit", nil) - return - } - if limit < 1 || limit > 100 { - errors.WriteHTTPBadRequest(w, "invalid limit (<1 or >100)", nil) - return - } - } - inCursor := req.URL.Query().Get("cursor") - var ts int64 - if inCursor != "" { - parts := strings.Split(inCursor, "::") - if len(parts) != 2 { - errors.WriteHTTPBadRequest(w, "invalid cursor", nil) - return - } - ts, err = strconv.ParseInt(parts[0], 10, 64) - if err != nil { - errors.WriteHTTPBadRequest(w, "invalid cursor", nil) - return - } - } - var posts []model.FeedPost - outCursor := "" - if name == FEED_ALL_STREAMS { - posts, err = a.Model.ListFeedPostsByType("livestream", limit, ts) - if err != nil { - errors.WriteHTTPInternalServerError(w, "error listing feed posts", err) - return - } - if len(posts) > 0 { - last := posts[len(posts)-1] - ts := last.CreatedAt.UnixMilli() - outCursor = fmt.Sprintf("%d::%s", ts, last.CID) - } - } else if name == FEED_LIVE_STREAMS { - segs, err := a.Model.MostRecentSegments() - if err != nil { - errors.WriteHTTPInternalServerError(w, "error listing feed posts", err) - return - } - for _, seg := range segs { - ls, err := a.Model.GetLatestLivestreamForRepo(seg.RepoDID) - if err != nil { - errors.WriteHTTPInternalServerError(w, "error listing feed posts", err) - return - } - posts = append(posts, model.FeedPost{ - URI: ls.PostURI, - }) - } - } else { - errors.WriteHTTPBadRequest(w, "invalid feed name", nil) - return - } - res := bsky.FeedGetFeedSkeleton_Output{ - Feed: []*bsky.FeedDefs_SkeletonFeedPost{}, - } - if outCursor != "" { - res.Cursor = &outCursor - } - for _, post := range posts { - res.Feed = append(res.Feed, &bsky.FeedDefs_SkeletonFeedPost{ - Post: post.URI, - }) - } - bs, err := json.Marshal(res) - if err != nil { - errors.WriteHTTPInternalServerError(w, "error marshalling feed skeleton", err) - return - } - w.Write(bs) - } -} diff --git a/pkg/api/feeds_test.go b/pkg/api/feeds_test.go index af01a544e..778f64ec1 100644 --- a/pkg/api/feeds_test.go +++ b/pkg/api/feeds_test.go @@ -1,63 +1 @@ package api - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFeedSkeletonRE(t *testing.T) { - tests := []struct { - feed string - name string - did string - want bool - }{ - { - feed: "at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/app.bsky.feed.generator/atproto", - did: "did:plc:oio4hkxaop4ao4wz2pp3f4cr", - name: "atproto", - want: true, - }, - { - feed: "at://did:web:iame.li/app.bsky.feed.generator/feedwithnumbers123", - did: "did:web:iame.li", - name: "feedwithnumbers123", - want: true, - }, - { - feed: "at://did:web:iame.li/app.bsky.feed.generator/feed-with-dashes", - did: "did:web:iame.li", - name: "feed-with-dashes", - want: true, - }, - { - feed: "foo", - want: false, - }, - { - feed: "at:///app.bsky.feed.generator/feedwithnumbers123", - want: false, - }, - { - feed: "at://did:web:iame.li/app.bsky.feed.generator/", - want: false, - }, - { - feed: "at://did:web:iame.li/app.bsky.feed.generator/feedwithnumbers123/errantsuffix", - want: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - did, name, err := parseFeedSkeleton(test.feed) - if !test.want { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, test.did, did) - require.Equal(t, test.name, name) - }) - } -} diff --git a/pkg/config/config.go b/pkg/config/config.go index afc4c56c3..03e7931ff 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/peterbourgon/ff/v3" "golang.org/x/exp/rand" "stream.place/streamplace/pkg/aqtime" @@ -90,6 +91,7 @@ type CLI struct { SmearAudio bool ExternalSigning bool TracingEndpoint string + JWK jwk.Key dataDirFlags []*string } diff --git a/pkg/spxrpc/feed.go b/pkg/spxrpc/feed.go new file mode 100644 index 000000000..02059fb4b --- /dev/null +++ b/pkg/spxrpc/feed.go @@ -0,0 +1,86 @@ +package spxrpc + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/bluesky-social/indigo/api/bsky" + appbskytypes "github.com/bluesky-social/indigo/api/bsky" + "stream.place/streamplace/pkg/model" +) + +var FeedSkeletonRE = regexp.MustCompile(`^at://did:(web|plc):([a-z0-9\.\-]+)/app.bsky.feed.generator/([a-z0-9\.\-]+)$`) + +func parseFeedSkeleton(did string) (string, string, error) { + matches := FeedSkeletonRE.FindStringSubmatch(did) + if len(matches) != 4 { + return "", "", fmt.Errorf("invalid feed parameter: %s", did) + } + return fmt.Sprintf("did:%s:%s", matches[1], matches[2]), matches[3], nil +} + +const FEED_LIVE_STREAMS = "live-streams" +const FEED_ALL_STREAMS = "all-streams" + +func (s *Server) handleAppBskyFeedGetFeedSkeleton(ctx context.Context, inCursor string, feed string, limit int) (*appbskytypes.FeedGetFeedSkeleton_Output, error) { + _, name, err := parseFeedSkeleton(feed) + if err != nil { + return nil, err + } + var ts int64 + if inCursor != "" { + parts := strings.Split(inCursor, "::") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid cursor: %w", err) + } + ts, err = strconv.ParseInt(parts[0], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid cursor: %w", err) + } + } + var posts []model.FeedPost + outCursor := "" + if name == FEED_ALL_STREAMS { + posts, err = s.model.ListFeedPostsByType("livestream", limit, ts) + if err != nil { + return nil, err + } + if len(posts) > 0 { + last := posts[len(posts)-1] + ts := last.CreatedAt.UnixMilli() + outCursor = fmt.Sprintf("%d::%s", ts, last.CID) + } + } else if name == FEED_LIVE_STREAMS { + segs, err := s.model.MostRecentSegments() + if err != nil { + return nil, err + } + for _, seg := range segs { + ls, err := s.model.GetLatestLivestreamForRepo(seg.RepoDID) + if err != nil { + return nil, err + } + posts = append(posts, model.FeedPost{ + URI: ls.PostURI, + }) + } + } else { + return nil, fmt.Errorf("invalid feed name: %s", name) + } + res := bsky.FeedGetFeedSkeleton_Output{ + Feed: []*bsky.FeedDefs_SkeletonFeedPost{}, + } + if outCursor != "" { + res.Cursor = &outCursor + } + for _, post := range posts { + res.Feed = append(res.Feed, &bsky.FeedDefs_SkeletonFeedPost{ + Post: post.URI, + }) + } + return &res, nil + +} diff --git a/pkg/spxrpc/feed_test.go b/pkg/spxrpc/feed_test.go new file mode 100644 index 000000000..29d4083b5 --- /dev/null +++ b/pkg/spxrpc/feed_test.go @@ -0,0 +1,63 @@ +package spxrpc + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFeedSkeletonRE(t *testing.T) { + tests := []struct { + feed string + name string + did string + want bool + }{ + { + feed: "at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/app.bsky.feed.generator/atproto", + did: "did:plc:oio4hkxaop4ao4wz2pp3f4cr", + name: "atproto", + want: true, + }, + { + feed: "at://did:web:iame.li/app.bsky.feed.generator/feedwithnumbers123", + did: "did:web:iame.li", + name: "feedwithnumbers123", + want: true, + }, + { + feed: "at://did:web:iame.li/app.bsky.feed.generator/feed-with-dashes", + did: "did:web:iame.li", + name: "feed-with-dashes", + want: true, + }, + { + feed: "foo", + want: false, + }, + { + feed: "at:///app.bsky.feed.generator/feedwithnumbers123", + want: false, + }, + { + feed: "at://did:web:iame.li/app.bsky.feed.generator/", + want: false, + }, + { + feed: "at://did:web:iame.li/app.bsky.feed.generator/feedwithnumbers123/errantsuffix", + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + did, name, err := parseFeedSkeleton(test.feed) + if !test.want { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.did, did) + require.Equal(t, test.name, name) + }) + } +} diff --git a/pkg/spxrpc/spxrpc.go b/pkg/spxrpc/spxrpc.go new file mode 100644 index 000000000..c32ef7026 --- /dev/null +++ b/pkg/spxrpc/spxrpc.go @@ -0,0 +1,37 @@ +package spxrpc + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "stream.place/streamplace/pkg/config" + "stream.place/streamplace/pkg/model" +) + +type Server struct { + e *echo.Echo + cli *config.CLI + model model.Model +} + +func NewServer(cli *config.CLI, model model.Model) (*Server, error) { + e := echo.New() + s := &Server{ + e: e, + cli: cli, + model: model, + } + err := s.RegisterHandlersPlaceStream(e) + if err != nil { + return nil, err + } + err = s.RegisterHandlersAppBsky(e) + if err != nil { + return nil, err + } + return s, nil +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.e.ServeHTTP(w, r) +} diff --git a/pkg/spxrpc/stubs.go b/pkg/spxrpc/stubs.go new file mode 100644 index 000000000..0e03cdd4a --- /dev/null +++ b/pkg/spxrpc/stubs.go @@ -0,0 +1,56 @@ +package spxrpc + +import ( + "strconv" + + appbskytypes "github.com/bluesky-social/indigo/api/bsky" + "github.com/labstack/echo/v4" + "go.opentelemetry.io/otel" +) + +func (s *Server) RegisterHandlersAppBsky(e *echo.Echo) error { + e.GET("/xrpc/app.bsky.feed.getFeedSkeleton", s.HandleAppBskyFeedGetFeedSkeleton) + return nil +} + +func (s *Server) HandleAppBskyFeedGetFeedSkeleton(c echo.Context) error { + ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleAppBskyFeedGetFeedSkeleton") + defer span.End() + cursor := c.QueryParam("cursor") + feed := c.QueryParam("feed") + + var limit int + if p := c.QueryParam("limit"); p != "" { + var err error + limit, err = strconv.Atoi(p) + if err != nil { + return err + } + } else { + limit = 50 + } + var out *appbskytypes.FeedGetFeedSkeleton_Output + var handleErr error + // func (s *Server) handleAppBskyFeedGetFeedSkeleton(ctx context.Context,cursor string,feed string,limit int) (*appbskytypes.FeedGetFeedSkeleton_Output, error) + out, handleErr = s.handleAppBskyFeedGetFeedSkeleton(ctx, cursor, feed, limit) + if handleErr != nil { + return handleErr + } + return c.JSON(200, out) +} + +func (s *Server) RegisterHandlersChatBsky(e *echo.Echo) error { + return nil +} + +func (s *Server) RegisterHandlersComAtproto(e *echo.Echo) error { + return nil +} + +func (s *Server) RegisterHandlersPlaceStream(e *echo.Echo) error { + return nil +} + +func (s *Server) RegisterHandlersToolsOzone(e *echo.Echo) error { + return nil +} diff --git a/util/lexgen-server.json b/util/lexgen-server.json new file mode 100644 index 000000000..469603dfc --- /dev/null +++ b/util/lexgen-server.json @@ -0,0 +1,8 @@ +[ + { + "package": "streamplace", + "prefix": "place.stream", + "outdir": "./pkg/streamplace", + "import": "stream.place/streamplace" + } +] diff --git a/util/lexgen-build.json b/util/lexgen-types.json similarity index 100% rename from util/lexgen-build.json rename to util/lexgen-types.json