installed gin
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX sync_group_listings_sync_group_id_platform_shop_id_idx;
|
||||||
|
DROP INDEX sync_group_listing_drafts_account_id_platform_shop_id_idx;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE UNIQUE INDEX ON sync_group_listings (sync_group_id, platform, shop_id) NULLS DISTINCT;
|
||||||
|
CREATE UNIQUE INDEX ON sync_group_listing_drafts (account_id, platform, shop_id) NULLS DISTINCT;
|
||||||
@@ -9,6 +9,7 @@ require github.com/jackc/pgx/v5 v5.7.6
|
|||||||
require (
|
require (
|
||||||
github.com/angelbeltran/templater v0.1.0
|
github.com/angelbeltran/templater v0.1.0
|
||||||
github.com/coreos/go-oidc/v3 v3.8.0
|
github.com/coreos/go-oidc/v3 v3.8.0
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/google/uuid v1.5.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/lmittmann/tint v1.1.2
|
github.com/lmittmann/tint v1.1.2
|
||||||
github.com/oapi-codegen/runtime v1.1.2
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
@@ -22,35 +23,55 @@ require (
|
|||||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
|
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c // indirect
|
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c // indirect
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc3 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/getkin/kin-openapi v0.133.0 // indirect
|
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 // indirect
|
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 // indirect
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
|
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
|
||||||
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
|
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
|
||||||
github.com/spf13/cobra v1.10.2 // indirect
|
github.com/spf13/cobra v1.10.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
||||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||||
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
golang.org/x/crypto v0.46.0 // indirect
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/mod v0.31.0 // indirect
|
golang.org/x/mod v0.31.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/tools v0.40.0 // indirect
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
|||||||
@@ -7,17 +7,21 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
|||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
|
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
|
||||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109213122-ef9f8bd8865f h1:9KBIsU6vHBWD/BGvxaFl45SNOE5aWHibZ3F5MdfFPsQ=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109213122-ef9f8bd8865f/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109213220-20157174e154 h1:HFkK1uyOQ5nZgrQRyvebyLPMJGkouRAS26ljn8EsU0g=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109213220-20157174e154/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109224857-024377724e7e h1:bsEp3zk309jiuKMwi5pS2rjolwvVLCfpSwUSNM/MZ04=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109224857-024377724e7e/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks=
|
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c h1:BIa061RHaIVP9TjpIQf0PldVpQmeln/2U1Cf2w8ALNI=
|
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c h1:BIa061RHaIVP9TjpIQf0PldVpQmeln/2U1Cf2w8ALNI=
|
||||||
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks=
|
github.com/angelbeltran/concurry v0.0.0-20260109231127-8e2678d8928c/go.mod h1:Aii6F8lZCJb8Ns0d1FH35bDxNoyiuvwXd5BYJroJnks=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -33,17 +37,33 @@ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5ql
|
|||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
|
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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -62,6 +82,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@@ -79,7 +100,13 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -87,12 +114,21 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
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=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
@@ -118,6 +154,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -139,13 +177,22 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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.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=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
||||||
@@ -155,6 +202,9 @@ github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4Z
|
|||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||||
|
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -164,8 +214,6 @@ golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
|||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
|
||||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -203,6 +251,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -218,8 +268,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -255,3 +303,5 @@ gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"ruben/inventory2/internal/consts"
|
"ruben/inventory2/internal/consts"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
@@ -18,7 +19,8 @@ type (
|
|||||||
SyncGroupListing struct {
|
SyncGroupListing struct {
|
||||||
SyncGroupIDs
|
SyncGroupIDs
|
||||||
AccountShopIDs
|
AccountShopIDs
|
||||||
ListingID string
|
ListingID string
|
||||||
|
OrderIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncGroupIDs struct {
|
SyncGroupIDs struct {
|
||||||
@@ -28,7 +30,8 @@ type (
|
|||||||
|
|
||||||
SyncGroupListingDraft struct {
|
SyncGroupListingDraft struct {
|
||||||
AccountShopIDs
|
AccountShopIDs
|
||||||
ListingID string
|
ListingID string
|
||||||
|
OrderIndex int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,9 +99,10 @@ func (db *Store) GetSyncGroupListingDraft(ctx context.Context, acctID int64, ord
|
|||||||
}
|
}
|
||||||
|
|
||||||
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[struct {
|
r, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[struct {
|
||||||
Platform *Platform
|
Platform *Platform
|
||||||
Shop_id *string
|
Shop_id *string
|
||||||
Listing_id *string
|
Listing_id *string
|
||||||
|
Order_index *int
|
||||||
}])
|
}])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
@@ -115,7 +119,8 @@ func (db *Store) GetSyncGroupListingDraft(ctx context.Context, acctID int64, ord
|
|||||||
Platform: deref(r.Platform),
|
Platform: deref(r.Platform),
|
||||||
ShopID: deref(r.Shop_id),
|
ShopID: deref(r.Shop_id),
|
||||||
},
|
},
|
||||||
ListingID: deref(r.Listing_id),
|
ListingID: deref(r.Listing_id),
|
||||||
|
OrderIndex: orderIndex,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,17 +187,7 @@ func (db *Store) DeleteSyncGroupListingDraft(ctx context.Context, acctID int64,
|
|||||||
rows, err := db.db.Query(
|
rows, err := db.db.Query(
|
||||||
ctx,
|
ctx,
|
||||||
`
|
`
|
||||||
WITH updated_drafts AS (
|
WITH deleted_draft AS (
|
||||||
UPDATE
|
|
||||||
sync_group_listing_drafts
|
|
||||||
SET
|
|
||||||
order_index = (order_index - 1)
|
|
||||||
WHERE
|
|
||||||
account_id = @account_id
|
|
||||||
AND order_index > @order_index
|
|
||||||
RETURNING
|
|
||||||
order_index
|
|
||||||
), deleted_draft AS (
|
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
sync_group_listing_drafts
|
sync_group_listing_drafts
|
||||||
WHERE
|
WHERE
|
||||||
@@ -202,17 +197,17 @@ func (db *Store) DeleteSyncGroupListingDraft(ctx context.Context, acctID int64,
|
|||||||
true AS found
|
true AS found
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(dd.found, false) AS found,
|
COUNT(*) as num_rows,
|
||||||
(COALESCE(MAX(ud.order_index), -1) + 1) AS num_rows
|
COALESCE(dd.found, FALSE) as found
|
||||||
FROM
|
FROM
|
||||||
deleted_draft dd
|
sync_group_listing_drafts ld
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
updated_drafts ud
|
deleted_draft dd
|
||||||
ON true
|
ON TRUE
|
||||||
|
WHERE
|
||||||
|
account_id = @account_id
|
||||||
GROUP BY
|
GROUP BY
|
||||||
ud.order_index, dd.found
|
ld.account_id, dd.found
|
||||||
LIMIT
|
|
||||||
1
|
|
||||||
`,
|
`,
|
||||||
pgx.NamedArgs{
|
pgx.NamedArgs{
|
||||||
"account_id": acctID,
|
"account_id": acctID,
|
||||||
@@ -256,6 +251,8 @@ func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([
|
|||||||
sync_group_listing_drafts
|
sync_group_listing_drafts
|
||||||
WHERE
|
WHERE
|
||||||
account_id = @account_id
|
account_id = @account_id
|
||||||
|
ORDER BY
|
||||||
|
order_index ASC
|
||||||
`,
|
`,
|
||||||
pgx.NamedArgs{
|
pgx.NamedArgs{
|
||||||
"account_id": acctID,
|
"account_id": acctID,
|
||||||
@@ -278,8 +275,8 @@ func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
listings := make([]SyncGroupListingDraft, len(rs))
|
listings := make([]SyncGroupListingDraft, len(rs))
|
||||||
for _, r := range rs {
|
for i, r := range rs {
|
||||||
listings[r.Order_index] = SyncGroupListingDraft{
|
listings[i] = SyncGroupListingDraft{
|
||||||
AccountShopIDs: AccountShopIDs{
|
AccountShopIDs: AccountShopIDs{
|
||||||
AccountIDs: AccountIDs{
|
AccountIDs: AccountIDs{
|
||||||
AccountID: acctID,
|
AccountID: acctID,
|
||||||
@@ -287,10 +284,15 @@ func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([
|
|||||||
Platform: deref(r.Platform),
|
Platform: deref(r.Platform),
|
||||||
ShopID: deref(r.Shop_id),
|
ShopID: deref(r.Shop_id),
|
||||||
},
|
},
|
||||||
ListingID: deref(r.Listing_id),
|
ListingID: deref(r.Listing_id),
|
||||||
|
OrderIndex: r.Order_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(listings, func(a, b SyncGroupListingDraft) int {
|
||||||
|
return a.OrderIndex - b.OrderIndex
|
||||||
|
})
|
||||||
|
|
||||||
return listings, nil
|
return listings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,8 +399,8 @@ func (db *Store) SaveNewSyncGroup(ctx context.Context, acctID int64) (SyncGroup,
|
|||||||
}
|
}
|
||||||
|
|
||||||
listings := make([]SyncGroupListing, len(rs))
|
listings := make([]SyncGroupListing, len(rs))
|
||||||
for _, r := range rs {
|
for i, r := range rs {
|
||||||
listings[r.Order_index] = SyncGroupListing{
|
listings[i] = SyncGroupListing{
|
||||||
SyncGroupIDs: SyncGroupIDs{
|
SyncGroupIDs: SyncGroupIDs{
|
||||||
AccountIDs: AccountIDs{
|
AccountIDs: AccountIDs{
|
||||||
AccountID: acctID,
|
AccountID: acctID,
|
||||||
@@ -412,10 +414,15 @@ func (db *Store) SaveNewSyncGroup(ctx context.Context, acctID int64) (SyncGroup,
|
|||||||
Platform: r.Platform,
|
Platform: r.Platform,
|
||||||
ShopID: r.Shop_id,
|
ShopID: r.Shop_id,
|
||||||
},
|
},
|
||||||
ListingID: r.Listing_id,
|
ListingID: r.Listing_id,
|
||||||
|
OrderIndex: r.Order_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(listings, func(a, b SyncGroupListing) int {
|
||||||
|
return a.OrderIndex - b.OrderIndex
|
||||||
|
})
|
||||||
|
|
||||||
return SyncGroup{
|
return SyncGroup{
|
||||||
SyncGroupIDs: SyncGroupIDs{
|
SyncGroupIDs: SyncGroupIDs{
|
||||||
AccountIDs: AccountIDs{
|
AccountIDs: AccountIDs{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const (
|
|||||||
AUTH0_CLIENT_SECRET = "83U-iWdVaNnwk9XDzteo_2VMyOq_l1siKYqg1_2E7jCzgL8MnkaxlysPMcPMGlxA"
|
AUTH0_CLIENT_SECRET = "83U-iWdVaNnwk9XDzteo_2VMyOq_l1siKYqg1_2E7jCzgL8MnkaxlysPMcPMGlxA"
|
||||||
|
|
||||||
// The Callback URL of our application.
|
// The Callback URL of our application.
|
||||||
AUTH0_CALLBACK_URL = "https://inventory-plus-plus.com/auth/login/callback"
|
AUTH0_CALLBACK_URL = "https://inventory-plus-plus.com/api/auth/login/callback"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -9,44 +9,41 @@ import (
|
|||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/middleware"
|
"ruben/inventory2/internal/server/middleware"
|
||||||
"ruben/inventory2/internal/server/response"
|
"ruben/inventory2/internal/server/response"
|
||||||
"ruben/inventory2/internal/server/router"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type accountSubrouter struct {
|
type accountSubrouter struct {
|
||||||
log *logging.Logger
|
log *logging.Logger
|
||||||
*router.SubMux
|
|
||||||
accts *accounts.Store
|
accts *accounts.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccountSubrouter(
|
func Routes(
|
||||||
|
r *gin.RouterGroup,
|
||||||
logger *logging.Logger,
|
logger *logging.Logger,
|
||||||
accts *accounts.Store,
|
accts *accounts.Store,
|
||||||
authMiddleware *middleware.Auth,
|
authMiddleware *middleware.Auth,
|
||||||
) *accountSubrouter {
|
) {
|
||||||
mux := router.NewSubMux(logger)
|
|
||||||
|
|
||||||
as := &accountSubrouter{
|
as := &accountSubrouter{
|
||||||
log: logger,
|
log: logger,
|
||||||
SubMux: mux,
|
accts: accts,
|
||||||
accts: accts,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withAuth := func(fn response.HandlerFunc) response.HandlerFunc {
|
withAuth := func(fn response.HandlerFunc) gin.HandlerFunc {
|
||||||
return authMiddleware.AuthenticateAndAddIdentity(fn)
|
return response.Handler(authMiddleware.AuthenticateAndAddIdentity(fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.Handle("POST /{acctID}/inventory/sync-groups/draft/listings", withAuth(as.createSyncGroupListingDraft))
|
r.POST("/:acctID/inventory/sync-groups/draft/listings", withAuth(as.createSyncGroupListingDraft))
|
||||||
mux.Handle("PUT /{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/shop", withAuth(as.setShopInSyncGroupListingDraft))
|
r.PUT("/:acctID/inventory/sync-groups/draft/listings/:orderIndex/shop", withAuth(as.setShopInSyncGroupListingDraft))
|
||||||
mux.Handle("PUT /{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/listing", withAuth(as.setListingInSyncGroupListingDraft))
|
r.PUT("/:acctID/inventory/sync-groups/draft/listings/:orderIndex/listing", withAuth(as.setListingInSyncGroupListingDraft))
|
||||||
mux.Handle("DELETE /{acctID}/inventory/sync-groups/draft/listings/{orderIndex}", withAuth(as.deleteSyncGroupListingDraft))
|
r.DELETE("/:acctID/inventory/sync-groups/draft/listings/:orderIndex", withAuth(as.deleteSyncGroupListingDraft))
|
||||||
mux.Handle("POST /{acctID}/inventory/sync-groups", withAuth(as.saveNewSyncGroup))
|
r.POST("/:acctID/inventory/sync-groups", withAuth(as.saveNewSyncGroup))
|
||||||
|
|
||||||
return as
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /accounts
|
// POST /api/accounts
|
||||||
func (s *accountSubrouter) createAccount(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) createAccount(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
email := r.FormValue("email")
|
email := r.FormValue("email")
|
||||||
if email == "" {
|
if email == "" {
|
||||||
@@ -64,11 +61,12 @@ func (s *accountSubrouter) createAccount(r *http.Request) (response.Response, er
|
|||||||
return nil, response.Errorf("failed to create account: %w", err)
|
return nil, response.Errorf("failed to create account: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.SeeOther(fmt.Sprintf("/accounts/%d", acct.AccountID)), nil
|
return response.SeeOther(fmt.Sprintf("/api/accounts/%d", acct.AccountID)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /accounts/{acctID}/inventory/sync-groups/draft/listings
|
// POST /api/accounts/{acctID}/inventory/sync-groups/draft/listings
|
||||||
func (s *accountSubrouter) createSyncGroupListingDraft(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) createSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
||||||
|
|
||||||
@@ -79,18 +77,19 @@ func (s *accountSubrouter) createSyncGroupListingDraft(r *http.Request) (respons
|
|||||||
|
|
||||||
return response.Redirect(
|
return response.Redirect(
|
||||||
http.StatusSeeOther,
|
http.StatusSeeOther,
|
||||||
fmt.Sprintf("/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
fmt.Sprintf("/ui/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/shop
|
// PUT /api/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/shop
|
||||||
// @platform string
|
// @platform string
|
||||||
// @shopID string
|
// @shopID string
|
||||||
func (s *accountSubrouter) setShopInSyncGroupListingDraft(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
||||||
|
|
||||||
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(r)
|
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -114,16 +113,17 @@ func (s *accountSubrouter) setShopInSyncGroupListingDraft(r *http.Request) (resp
|
|||||||
|
|
||||||
return response.Redirect(
|
return response.Redirect(
|
||||||
http.StatusSeeOther,
|
http.StatusSeeOther,
|
||||||
fmt.Sprintf("/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
fmt.Sprintf("/ui/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/listing
|
// PUT /api/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}/listing
|
||||||
func (s *accountSubrouter) setListingInSyncGroupListingDraft(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) setListingInSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
||||||
|
|
||||||
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(r)
|
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -140,16 +140,17 @@ func (s *accountSubrouter) setListingInSyncGroupListingDraft(r *http.Request) (r
|
|||||||
|
|
||||||
return response.Redirect(
|
return response.Redirect(
|
||||||
http.StatusSeeOther,
|
http.StatusSeeOther,
|
||||||
fmt.Sprintf("/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
fmt.Sprintf("/ui/accounts/%d/inventory/sync-groups/draft/listings/%d", acctID, orderIndex),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE /accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}
|
// DELETE /api/accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}
|
||||||
func (s *accountSubrouter) deleteSyncGroupListingDraft(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) deleteSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
||||||
|
|
||||||
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(r)
|
orderIndex, err := getOrderIndexForSyncGroupListingDraftFromPath(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -161,8 +162,9 @@ func (s *accountSubrouter) deleteSyncGroupListingDraft(r *http.Request) (respons
|
|||||||
return response.Status(200), nil
|
return response.Status(200), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /accounts/{acctID}/inventory/sync-groups
|
// POST /api/accounts/{acctID}/inventory/sync-groups
|
||||||
func (s *accountSubrouter) saveNewSyncGroup(r *http.Request) (response.Response, error) {
|
func (s *accountSubrouter) saveNewSyncGroup(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
acctID := middleware.GetIdentity(ctx).Account.AccountID
|
||||||
|
|
||||||
@@ -174,12 +176,12 @@ func (s *accountSubrouter) saveNewSyncGroup(r *http.Request) (response.Response,
|
|||||||
return response.Redirect(
|
return response.Redirect(
|
||||||
http.StatusSeeOther,
|
http.StatusSeeOther,
|
||||||
// TODO: template not implemented
|
// TODO: template not implemented
|
||||||
fmt.Sprintf("/accounts/%d/inventory/sync-groups/%d", acctID, grp.SyncGroupID),
|
fmt.Sprintf("/ui/accounts/%d/inventory/sync-groups/%d", acctID, grp.SyncGroupID),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrderIndexForSyncGroupListingDraftFromPath(r *http.Request) (int, error) {
|
func getOrderIndexForSyncGroupListingDraftFromPath(c *gin.Context) (int, error) {
|
||||||
orderIndexStr := r.PathValue("orderIndex")
|
orderIndexStr := c.Param("orderIndex")
|
||||||
if orderIndexStr == "" {
|
if orderIndexStr == "" {
|
||||||
return 0, response.NotFound().
|
return 0, response.NotFound().
|
||||||
Msg("no orderIndex found")
|
Msg("no orderIndex found")
|
||||||
|
|||||||
@@ -3,53 +3,36 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"ruben/inventory2/internal/domains/authentication"
|
"ruben/inventory2/internal/domains/authentication"
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/cookies"
|
"ruben/inventory2/internal/server/cookies"
|
||||||
"ruben/inventory2/internal/server/response"
|
"ruben/inventory2/internal/server/response"
|
||||||
"ruben/inventory2/internal/server/router"
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginSubrouter struct {
|
type loginSubrouter struct {
|
||||||
log *logging.Logger
|
log *logging.Logger
|
||||||
auth *authentication.Authenticator
|
auth *authentication.Authenticator
|
||||||
router.Subrouter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginSubrouter(logger *logging.Logger, auth *authentication.Authenticator) *loginSubrouter {
|
func Routes(
|
||||||
mux := router.NewSubMux(logger)
|
r *gin.RouterGroup,
|
||||||
|
logger *logging.Logger,
|
||||||
|
auth *authentication.Authenticator,
|
||||||
|
) {
|
||||||
ls := &loginSubrouter{
|
ls := &loginSubrouter{
|
||||||
log: logger,
|
log: logger,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
Subrouter: mux,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.Handle("GET /login", ls.loginPage)
|
r.GET("/login", response.Handler(ls.loginPage))
|
||||||
mux.Handle("GET /login/callback", ls.loginCallback)
|
r.GET("/login/callback", response.Handler(ls.loginCallback))
|
||||||
mux.Handle("GET /logout", ls.logoutPage)
|
r.GET("/logout", response.Handler(ls.logoutPage))
|
||||||
|
|
||||||
return ls
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *loginSubrouter) newLoginSubrouter(logger *logging.Logger) router.Subrouter {
|
func (s *loginSubrouter) loginPage(c *gin.Context) (response.Response, error) {
|
||||||
mux := router.NewSubMux(logger)
|
r := c.Request
|
||||||
|
|
||||||
ls := &loginSubrouter{
|
|
||||||
log: s.log,
|
|
||||||
auth: s.auth,
|
|
||||||
Subrouter: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.Handle("GET /login", ls.loginPage)
|
|
||||||
mux.Handle("GET /login/callback", ls.loginCallback)
|
|
||||||
mux.Handle("GET /logout", ls.logoutPage)
|
|
||||||
|
|
||||||
return ls
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *loginSubrouter) loginPage(r *http.Request) (response.Response, error) {
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
u, err := NewLoginURL(ctx, s.auth, "/")
|
u, err := NewLoginURL(ctx, s.auth, "/")
|
||||||
@@ -71,7 +54,8 @@ func NewLoginURL(ctx context.Context, auth *authentication.Authenticator, target
|
|||||||
return auth.AuthCodeURL(base64EncodedState), nil
|
return auth.AuthCodeURL(base64EncodedState), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *loginSubrouter) loginCallback(r *http.Request) (response.Response, error) {
|
func (s *loginSubrouter) loginCallback(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
|
|
||||||
@@ -90,7 +74,9 @@ func (s *loginSubrouter) loginCallback(r *http.Request) (response.Response, erro
|
|||||||
Cookie(cookies.AccessToken(accessToken, expiration)), nil
|
Cookie(cookies.AccessToken(accessToken, expiration)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *loginSubrouter) logoutPage(r *http.Request) (response.Response, error) {
|
func (s *loginSubrouter) logoutPage(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
host := r.Header.Get("X-Forwarded-Host")
|
host := r.Header.Get("X-Forwarded-Host")
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = r.Host
|
host = r.Host
|
||||||
|
|||||||
@@ -17,55 +17,74 @@ import (
|
|||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/middleware"
|
"ruben/inventory2/internal/server/middleware"
|
||||||
"ruben/inventory2/internal/server/response"
|
"ruben/inventory2/internal/server/response"
|
||||||
"ruben/inventory2/internal/server/router"
|
|
||||||
|
|
||||||
"github.com/angelbeltran/templater"
|
"github.com/angelbeltran/templater"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type webpageRouter struct {
|
type (
|
||||||
log *logging.Logger
|
webpageRouter struct {
|
||||||
contentDir string
|
log *logging.Logger
|
||||||
templater *templater.Templater
|
contentDir string
|
||||||
rawEvents *raw_events.Store
|
templater *templater.Templater
|
||||||
accts *accounts.Store
|
rawEvents *raw_events.Store
|
||||||
etsy *etsy_platform.Platform
|
accts *accounts.Store
|
||||||
router.Subrouter
|
etsy *etsy_platform.Platform
|
||||||
}
|
authMiddleware *middleware.Auth
|
||||||
|
}
|
||||||
|
|
||||||
func NewWebpageRouter(
|
// ErrTemplateNotFound is returned if the reason the template failed to compile
|
||||||
|
// is due to the template not being found.
|
||||||
|
ErrTemplateNotFound struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupRoutes(
|
||||||
logger *logging.Logger,
|
logger *logging.Logger,
|
||||||
|
r gin.IRouter,
|
||||||
contentDir string,
|
contentDir string,
|
||||||
tmpl *templater.Templater,
|
tmpl *templater.Templater,
|
||||||
rawEvents *raw_events.Store,
|
rawEvents *raw_events.Store,
|
||||||
accts *accounts.Store,
|
accts *accounts.Store,
|
||||||
etsy *etsy_platform.Platform,
|
etsy *etsy_platform.Platform,
|
||||||
authMiddleware *middleware.Auth,
|
authMiddleware *middleware.Auth,
|
||||||
) *webpageRouter {
|
) {
|
||||||
mux := router.NewSubMux(logger)
|
s := &webpageRouter{
|
||||||
|
log: logger,
|
||||||
wr := &webpageRouter{
|
contentDir: contentDir,
|
||||||
log: logger,
|
templater: tmpl,
|
||||||
contentDir: contentDir,
|
rawEvents: rawEvents,
|
||||||
templater: tmpl,
|
accts: accts,
|
||||||
rawEvents: rawEvents,
|
etsy: etsy,
|
||||||
accts: accts,
|
authMiddleware: authMiddleware,
|
||||||
etsy: etsy,
|
|
||||||
Subrouter: mux,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-authenticated
|
fn2 := s.authMiddleware.AuthenticateAndAddIdentity(s.serveTemplates)
|
||||||
mux.Handle("GET /{$}", authMiddleware.AddIdentity(wr.serveTemplates))
|
|
||||||
|
|
||||||
// authenticated
|
r.GET("/*rest", response.Handler(func(c *gin.Context) (response.Response, error) {
|
||||||
mux.Handle("GET /", authMiddleware.AuthenticateAndAddIdentity(wr.serveTemplates))
|
c.Request.URL.Path = c.Request.URL.Path[3:]
|
||||||
|
defer func() {
|
||||||
|
c.Request.URL.Path = "/ui" + c.Request.URL.Path
|
||||||
|
}()
|
||||||
|
|
||||||
return wr
|
p := c.Request.URL.Path
|
||||||
|
if p == "/" || p == "" {
|
||||||
|
c, err := s.authMiddleware.AddIdentityToRequest(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.serveTemplates(c)
|
||||||
|
}
|
||||||
|
return fn2(c)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /
|
// GET /
|
||||||
// compiles the page template or component template matching the url
|
// compiles the page template or component template matching the url
|
||||||
// func (s *Server) serveTemplates(r *http.Request) (response.Response, error) {
|
// func (s *Server) serveTemplates(r *http.Request) (response.Response, error) {
|
||||||
func (s *webpageRouter) serveTemplates(r *http.Request) (response.Response, error) {
|
func (s *webpageRouter) serveTemplates(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
name, args := s.getTemplateNameAndArgs(r, s.contentDir+"/templates/component_bodies")
|
name, args := s.getTemplateNameAndArgs(r, s.contentDir+"/templates/component_bodies")
|
||||||
|
|
||||||
b, err := s.templater.ExecuteComponentBody(name, args...)
|
b, err := s.templater.ExecuteComponentBody(name, args...)
|
||||||
@@ -190,7 +209,9 @@ func getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(file
|
|||||||
func (s *webpageRouter) handleTemplateError(err error, templateArgs ...any) (response.Response, error) {
|
func (s *webpageRouter) handleTemplateError(err error, templateArgs ...any) (response.Response, error) {
|
||||||
if isFileNotFoundError(err) {
|
if isFileNotFoundError(err) {
|
||||||
return nil, response.NotFound().
|
return nil, response.NotFound().
|
||||||
Wrap(err).
|
Wrap(ErrTemplateNotFound{
|
||||||
|
err: err,
|
||||||
|
}).
|
||||||
Msg("resource not found")
|
Msg("resource not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,3 +301,14 @@ func authorizeByMatchingAccountID(r *http.Request, acctIDPathPosition int) error
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ErrTemplateNotFound) Error() string {
|
||||||
|
if e.err != nil {
|
||||||
|
return fmt.Sprintf("template not found: %v", e.err)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("template not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrTemplateNotFound) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,58 +3,60 @@ package etsy
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ruben/inventory2/internal/domains/platforms/etsy"
|
"ruben/inventory2/internal/domains/platforms/etsy"
|
||||||
"ruben/inventory2/internal/domains/raw_events"
|
"ruben/inventory2/internal/domains/raw_events"
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
|
"ruben/inventory2/internal/server/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Webhooks struct {
|
|
||||||
log *logging.Logger
|
|
||||||
cfg Config
|
|
||||||
db *raw_events.Store
|
|
||||||
etsy *etsy.Platform
|
|
||||||
}
|
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
OAuthRedirectURIWithAcctIDParam string
|
OAuthRedirectURIWithAcctIDParam string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWebhookHandler(
|
func Webhooks(
|
||||||
|
r *gin.RouterGroup,
|
||||||
logger *logging.Logger,
|
logger *logging.Logger,
|
||||||
db *raw_events.Store,
|
db *raw_events.Store,
|
||||||
platform *etsy.Platform,
|
platform *etsy.Platform,
|
||||||
cfg Config,
|
cfg Config,
|
||||||
) http.Handler {
|
) {
|
||||||
h := Webhooks{
|
h := webhooks{
|
||||||
log: logger,
|
log: logger,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
db: db,
|
db: db,
|
||||||
etsy: platform,
|
etsy: platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
r.POST("/test", response.Handler(h.test))
|
||||||
|
|
||||||
mux.HandleFunc("POST /test", h.test)
|
r.GET(h.cfg.OAuthRedirectURIWithAcctIDParam, response.Handler(h.redirectURI))
|
||||||
|
|
||||||
mux.HandleFunc("GET "+h.cfg.OAuthRedirectURIWithAcctIDParam, h.redirectURI)
|
r.GET("/{acctID}/new-account-link", response.Handler(h.newAccountLink))
|
||||||
|
|
||||||
mux.HandleFunc("GET /{acctID}/new-account-link", h.newAccountLink)
|
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
webhooks struct {
|
||||||
|
log *logging.Logger
|
||||||
|
cfg Config
|
||||||
|
db *raw_events.Store
|
||||||
|
etsy *etsy.Platform
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// POST /test
|
// POST /test
|
||||||
func (h Webhooks) test(w http.ResponseWriter, r *http.Request) {
|
func (h webhooks) test(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
var body json.RawMessage
|
var body json.RawMessage
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
http.Error(w, "Failed to decode body as json: "+err.Error(), 500)
|
return nil, response.Errorf("failed to decode body as json: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := time.Now().UTC()
|
ts := time.Now().UTC()
|
||||||
@@ -75,21 +77,21 @@ func (h Webhooks) test(w http.ResponseWriter, r *http.Request) {
|
|||||||
Payload: body,
|
Payload: body,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error occurred saving the body as the event payload: "+err.Error(), 500)
|
return nil, response.Errorf("error occurred saving the body as the event payload: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(201)
|
return response.Status(201), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET h.cfg.OAuthRedirectURIWithAcctIDParam
|
// GET h.cfg.OAuthRedirectURIWithAcctIDParam
|
||||||
func (h Webhooks) redirectURI(w http.ResponseWriter, r *http.Request) {
|
func (h webhooks) redirectURI(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
// get account id for the request
|
// get account id for the request
|
||||||
|
|
||||||
acctID, err := strconv.ParseInt(r.PathValue("acctID"), 10, 64)
|
acctID, err := strconv.ParseInt(c.Param("acctID"), 10, 64)
|
||||||
if err != nil || acctID <= 0 {
|
if err != nil || acctID <= 0 {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
return nil, response.NotFound()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
@@ -113,41 +115,38 @@ func (h Webhooks) redirectURI(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
h.etsy.InvalidateState(ctx, state)
|
h.etsy.InvalidateState(ctx, state)
|
||||||
|
|
||||||
return
|
return response.Status(200), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the state to prevent CSRF attacks
|
// validate the state to prevent CSRF attacks
|
||||||
|
|
||||||
ok, err := h.etsy.HandleNewAuthCode(ctx, acctID, state, q.Get("code"))
|
ok, err := h.etsy.HandleNewAuthCode(ctx, acctID, state, q.Get("code"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
h.log.Error("failed to handle new auth code", "error", err)
|
h.log.Error("failed to handle new auth code", "error", err)
|
||||||
return
|
return nil, response.Forbidden()
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
return nil, response.Forbidden()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to the user's account page
|
// redirect to the user's account page
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/accounts/%d", acctID), http.StatusSeeOther)
|
return response.SeeOther(fmt.Sprintf("/accounts/%d", acctID)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /{acctID}/new-account-link
|
// GET /{acctID}/new-account-link
|
||||||
func (h Webhooks) newAccountLink(w http.ResponseWriter, r *http.Request) {
|
func (h webhooks) newAccountLink(c *gin.Context) (response.Response, error) {
|
||||||
acctIDStr := r.PathValue("acctID")
|
r := c.Request
|
||||||
|
acctIDStr := c.Param("acctID")
|
||||||
acctID, err := strconv.ParseInt(acctIDStr, 10, 64)
|
acctID, err := strconv.ParseInt(acctIDStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("account %s not found", acctIDStr), http.StatusNotFound)
|
return nil, response.NotFound().Msgf("account %s not found", acctIDStr)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := h.etsy.GenerateConnectionURLForNewAccount(r.Context(), acctID)
|
u, err := h.etsy.GenerateConnectionURLForNewAccount(r.Context(), acctID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("failed to generate url for account %d: %v", acctID, err), http.StatusInternalServerError)
|
return nil, fmt.Errorf("failed to generate url for account %d: %w", acctID, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
|
return response.TemporaryRedirect(u.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,26 @@ package tiktok
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ruben/inventory2/internal/domains/raw_events"
|
"ruben/inventory2/internal/domains/raw_events"
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
|
"ruben/inventory2/internal/server/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWebhookHandler(logger *logging.Logger, db *raw_events.Store) http.Handler {
|
func Webhooks(
|
||||||
mux := http.NewServeMux()
|
r *gin.RouterGroup,
|
||||||
|
logger *logging.Logger,
|
||||||
|
db *raw_events.Store,
|
||||||
|
) {
|
||||||
|
r.POST("/test", response.Handler(func(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
mux.HandleFunc("POST /test", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var body json.RawMessage
|
var body json.RawMessage
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
http.Error(w, "Failed to decode body as json: "+err.Error(), 500)
|
return nil, fmt.Errorf("failed to decode body as json: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := time.Now().UTC()
|
ts := time.Now().UTC()
|
||||||
@@ -30,12 +35,9 @@ func NewWebhookHandler(logger *logging.Logger, db *raw_events.Store) http.Handle
|
|||||||
Payload: body,
|
Payload: body,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error occurred saving the body as the event payload: "+err.Error(), 500)
|
return nil, fmt.Errorf("error occurred saving the body as the event payload: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(201)
|
return response.Status(201), nil
|
||||||
})
|
}))
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,42 @@
|
|||||||
package webhooks
|
package webhooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
etsy_platform "ruben/inventory2/internal/domains/platforms/etsy"
|
etsy_platform "ruben/inventory2/internal/domains/platforms/etsy"
|
||||||
"ruben/inventory2/internal/domains/raw_events"
|
"ruben/inventory2/internal/domains/raw_events"
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/api/webhooks/etsy"
|
"ruben/inventory2/internal/server/api/webhooks/etsy"
|
||||||
"ruben/inventory2/internal/server/api/webhooks/tiktok"
|
"ruben/inventory2/internal/server/api/webhooks/tiktok"
|
||||||
"ruben/inventory2/internal/server/api/webhooks/wix"
|
"ruben/inventory2/internal/server/api/webhooks/wix"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Etsy etsy.Config
|
Etsy etsy.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: just move over to the 'site' package, and then consider renaming the site package to something else?
|
func Webhooks(
|
||||||
func New(logger *logging.Logger, eventsDB *raw_events.Store, etsyPlatform *etsy_platform.Platform, cfg Config) http.Handler {
|
r *gin.RouterGroup,
|
||||||
wh := http.NewServeMux()
|
logger *logging.Logger,
|
||||||
|
eventsDB *raw_events.Store,
|
||||||
wh.Handle(
|
etsyPlatform *etsy_platform.Platform,
|
||||||
"/etsy/",
|
cfg Config,
|
||||||
http.StripPrefix("/etsy", etsy.NewWebhookHandler(
|
) {
|
||||||
logger.WithGroup("etsy"),
|
etsy.Webhooks(
|
||||||
eventsDB,
|
r.Group("/etsy"),
|
||||||
etsyPlatform,
|
logger.WithGroup("etsy"),
|
||||||
cfg.Etsy,
|
eventsDB,
|
||||||
)),
|
etsyPlatform,
|
||||||
|
cfg.Etsy,
|
||||||
)
|
)
|
||||||
wh.Handle(
|
tiktok.Webhooks(
|
||||||
"/tiktok/",
|
r.Group("/tiktok"),
|
||||||
http.StripPrefix("/tiktok", tiktok.NewWebhookHandler(
|
logger.WithGroup("tiktok"),
|
||||||
logger.WithGroup("tiktok"),
|
eventsDB,
|
||||||
eventsDB,
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
wh.Handle(
|
wix.Webhooks(
|
||||||
"/wix/",
|
r.Group("/wix"),
|
||||||
http.StripPrefix("/wix", wix.NewWebhookHandler(
|
logger.WithGroup("wix"),
|
||||||
logger.WithGroup("wix"),
|
eventsDB,
|
||||||
eventsDB,
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return wh
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,26 @@ package wix
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ruben/inventory2/internal/domains/raw_events"
|
"ruben/inventory2/internal/domains/raw_events"
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
|
"ruben/inventory2/internal/server/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWebhookHandler(logger *logging.Logger, db *raw_events.Store) http.Handler {
|
func Webhooks(
|
||||||
mux := http.NewServeMux()
|
r *gin.RouterGroup,
|
||||||
|
logger *logging.Logger,
|
||||||
|
db *raw_events.Store,
|
||||||
|
) {
|
||||||
|
r.POST("/test", response.Handler(func(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
mux.HandleFunc("POST /test", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var body json.RawMessage
|
var body json.RawMessage
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
http.Error(w, "Failed to decode body as json: "+err.Error(), 500)
|
return nil, fmt.Errorf("failed to decode body as json: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := time.Now().UTC()
|
ts := time.Now().UTC()
|
||||||
@@ -30,12 +35,9 @@ func NewWebhookHandler(logger *logging.Logger, db *raw_events.Store) http.Handle
|
|||||||
Payload: body,
|
Payload: body,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error occurred saving the body as the event payload: "+err.Error(), 500)
|
return nil, fmt.Errorf("error occurred saving the body as the event payload: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(201)
|
return response.Status(201), nil
|
||||||
})
|
}))
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ruben/inventory2/internal/consts"
|
"ruben/inventory2/internal/consts"
|
||||||
@@ -15,6 +14,8 @@ import (
|
|||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/cookies"
|
"ruben/inventory2/internal/server/cookies"
|
||||||
"ruben/inventory2/internal/server/response"
|
"ruben/inventory2/internal/server/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -52,20 +53,22 @@ func NewAuth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) AddIdentity(fn response.HandlerFunc) response.HandlerFunc {
|
func (a *Auth) AddIdentity(fn response.HandlerFunc) response.HandlerFunc {
|
||||||
return func(r *http.Request) (response.Response, error) {
|
return func(c *gin.Context) (response.Response, error) {
|
||||||
r, err := a.AddIdentityToRequest(r)
|
c, err := a.AddIdentityToRequest(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(r)
|
return fn(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
|
func (a *Auth) AddIdentityToRequest(c *gin.Context) (*gin.Context, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
ck, err := r.Cookie("access_token")
|
ck, err := r.Cookie("access_token")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
@@ -75,27 +78,29 @@ func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
|
|||||||
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
|
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, consts.ErrNotFound) {
|
if errors.Is(err, consts.ErrNotFound) {
|
||||||
return r, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, response.Errorf("failed to load authentication details: %w", err)
|
return c, response.Errorf("failed to load authentication details: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expiration.Before(time.Now()) {
|
if expiration.Before(time.Now()) {
|
||||||
return r, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
|
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r, response.Errorf("failed to load user and account defails: %w", err)
|
return c, response.Errorf("failed to load user and account defails: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.WithContext(SetIdentity(ctx, Identity{
|
c.Request = r.WithContext(SetIdentity(ctx, Identity{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
Claims: claims,
|
Claims: claims,
|
||||||
User: user,
|
User: user,
|
||||||
Account: acct,
|
Account: acct,
|
||||||
})), nil
|
}))
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: after getting auth, consider making http handler functions take 'claims', etc, as function arguments
|
// TODO: after getting auth, consider making http handler functions take 'claims', etc, as function arguments
|
||||||
@@ -103,73 +108,86 @@ func (a *Auth) AddIdentityToRequest(r *http.Request) (*http.Request, error) {
|
|||||||
|
|
||||||
// auth middleware to verify access_token cookie and set custom claims in the request context
|
// auth middleware to verify access_token cookie and set custom claims in the request context
|
||||||
func (a *Auth) AuthenticateAndAddIdentity(f response.HandlerFunc, assertions ...AuthorizationAssertions) response.HandlerFunc {
|
func (a *Auth) AuthenticateAndAddIdentity(f response.HandlerFunc, assertions ...AuthorizationAssertions) response.HandlerFunc {
|
||||||
return func(r *http.Request) (response.Response, error) {
|
return func(c *gin.Context) (response.Response, error) {
|
||||||
ck, err := r.Cookie("access_token")
|
c, res, err := a.AuthenticateAndAddIdentityToRequest(c, assertions...)
|
||||||
if err != nil {
|
if res != nil || err != nil {
|
||||||
return response.TemporaryRedirect("/").
|
return res, nil
|
||||||
JSON("no access_token cookie provided"), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
return f(c)
|
||||||
|
|
||||||
accessToken := ck.Value
|
|
||||||
|
|
||||||
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, consts.ErrNotFound) {
|
|
||||||
u, err := a.newLoginURL(ctx, a.auth, r.URL.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, response.Errorf("failed to generate login url: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.TemporaryRedirect(u), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, response.Errorf("failed to authenticate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
// refresh tokens, when the access token is "old enough"
|
|
||||||
|
|
||||||
// id token lifetime is 48 hours, allowing a person to use the app everyday comfortably, with wiggle room, without having to log in.
|
|
||||||
const idTokenLifetime = 48 * time.Hour
|
|
||||||
if refreshFloor := expiration.Add(-(idTokenLifetime / 4)); refreshFloor.Before(now) {
|
|
||||||
accessToken, expiration, err = a.auth.RefreshAccessToken(ctx, accessToken)
|
|
||||||
if err != nil {
|
|
||||||
a.log.Warn("failed to refresh access token", "error", err)
|
|
||||||
return response.TemporaryRedirect("/").
|
|
||||||
Body(io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("failed to refresh access token: %v", err))))).
|
|
||||||
Cookie(cookies.Expired("access_token")), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'redirect' to same url, to set the new access_token cookie
|
|
||||||
return response.TemporaryRedirect(r.URL.String()).
|
|
||||||
Cookie(cookies.AccessToken(accessToken, expiration)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// add identity info to request context
|
|
||||||
|
|
||||||
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, response.Errorf("failed to authorize: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, as := range assertions {
|
|
||||||
if res, err := as(r); res != nil || err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return f(r.WithContext(SetIdentity(ctx, Identity{
|
|
||||||
AccessToken: accessToken,
|
|
||||||
Claims: claims,
|
|
||||||
User: user,
|
|
||||||
Account: acct,
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) AuthenticateAndAddIdentityToRequest(c *gin.Context, assertions ...AuthorizationAssertions) (*gin.Context, response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
ck, err := r.Cookie("access_token")
|
||||||
|
if err != nil {
|
||||||
|
return c, response.TemporaryRedirect("/").
|
||||||
|
JSON("no access_token cookie provided"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
accessToken := ck.Value
|
||||||
|
|
||||||
|
claims, expiration, err := a.auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, consts.ErrNotFound) {
|
||||||
|
u, err := a.newLoginURL(ctx, a.auth, r.URL.String())
|
||||||
|
if err != nil {
|
||||||
|
return c, nil, response.Errorf("failed to generate login url: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, response.TemporaryRedirect(u), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil, response.Errorf("failed to authenticate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
// refresh tokens, when the access token is "old enough"
|
||||||
|
|
||||||
|
// id token lifetime is 48 hours, allowing a person to use the app everyday comfortably, with wiggle room, without having to log in.
|
||||||
|
const idTokenLifetime = 48 * time.Hour
|
||||||
|
if refreshFloor := expiration.Add(-(idTokenLifetime / 4)); refreshFloor.Before(now) {
|
||||||
|
accessToken, expiration, err = a.auth.RefreshAccessToken(ctx, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Warn("failed to refresh access token", "error", err)
|
||||||
|
return c, response.TemporaryRedirect("/").
|
||||||
|
Body(io.NopCloser(bytes.NewBuffer([]byte(fmt.Sprintf("failed to refresh access token: %v", err))))).
|
||||||
|
Cookie(cookies.Expired("access_token")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'redirect' to same url, to set the new access_token cookie
|
||||||
|
return c, response.TemporaryRedirect(r.URL.String()).
|
||||||
|
Cookie(cookies.AccessToken(accessToken, expiration)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add identity info to request context
|
||||||
|
|
||||||
|
user, acct, err := a.accts.GetUserAndAccountByAccessToken(ctx, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
return c, nil, response.Errorf("failed to authorize: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, as := range assertions {
|
||||||
|
if res, err := as(c); res != nil || err != nil {
|
||||||
|
return c, res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.WithContext(SetIdentity(ctx, Identity{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
Claims: claims,
|
||||||
|
User: user,
|
||||||
|
Account: acct,
|
||||||
|
}))
|
||||||
|
c.Request = r
|
||||||
|
|
||||||
|
return c, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
type identityKey struct{}
|
type identityKey struct{}
|
||||||
|
|
||||||
// stores identity in request context
|
// stores identity in request context
|
||||||
|
|||||||
@@ -7,15 +7,18 @@ import (
|
|||||||
|
|
||||||
"ruben/inventory2/internal/logging"
|
"ruben/inventory2/internal/logging"
|
||||||
"ruben/inventory2/internal/server/response"
|
"ruben/inventory2/internal/server/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LogRequests(ctx context.Context, logger *logging.Logger) response.Middleware {
|
func LogRequests(ctx context.Context, logger *logging.Logger) response.Middleware {
|
||||||
reqIDCh := newRequestIDProvider(ctx)
|
reqIDCh := newRequestIDProvider(ctx)
|
||||||
|
|
||||||
return func(fn response.HandlerFunc) response.HandlerFunc {
|
return func(fn response.HandlerFunc) response.HandlerFunc {
|
||||||
return func(r *http.Request) (response.Response, error) {
|
return func(c *gin.Context) (response.Response, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
r := c.Request
|
||||||
args := []any{
|
args := []any{
|
||||||
"id", <-reqIDCh,
|
"id", <-reqIDCh,
|
||||||
"url", r.URL,
|
"url", r.URL,
|
||||||
@@ -24,7 +27,7 @@ func LogRequests(ctx context.Context, logger *logging.Logger) response.Middlewar
|
|||||||
|
|
||||||
logger.Debug("Request", args...)
|
logger.Debug("Request", args...)
|
||||||
|
|
||||||
res, err := fn(r)
|
res, err := fn(c)
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
|
|
||||||
var status int
|
var status int
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
package response
|
package response
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
HandlerFunc = func(r *http.Request) (Response, error)
|
HandlerFunc = func(c *gin.Context) (Response, error)
|
||||||
|
|
||||||
Middleware = func(HandlerFunc) HandlerFunc
|
Middleware = func(HandlerFunc) HandlerFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
func Handler(f HandlerFunc) http.HandlerFunc {
|
func Handler(f HandlerFunc) gin.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(c *gin.Context) {
|
||||||
res, err := f(r)
|
res, err := f(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(c, err)
|
||||||
} else {
|
} else {
|
||||||
Write(w, r, res)
|
Write(c, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Write(w http.ResponseWriter, r *http.Request, res Response) {
|
func Write(c *gin.Context, res Response) {
|
||||||
|
w := c.Writer
|
||||||
|
|
||||||
// w.Header() must be set before ResponseWriter.WriteHeader is called
|
// w.Header() must be set before ResponseWriter.WriteHeader is called
|
||||||
// or redirect is attempted
|
// or redirect is attempted
|
||||||
hdrs := w.Header()
|
hdrs := w.Header()
|
||||||
@@ -19,7 +23,7 @@ func Write(w http.ResponseWriter, r *http.Request, res Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if code, to, ok := res.GetRedirect(); ok {
|
if code, to, ok := res.GetRedirect(); ok {
|
||||||
http.Redirect(w, r, to, code.Int())
|
http.Redirect(w, c.Request, to, code.Int())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +51,8 @@ func Write(w http.ResponseWriter, r *http.Request, res Response) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteError(w http.ResponseWriter, err error) {
|
func WriteError(c *gin.Context, err error) {
|
||||||
http.Error(w, err.Error(), GetStatusFromError(err))
|
c.String(GetStatusFromError(err), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStatusFromError(err error) int {
|
func GetStatusFromError(err error) int {
|
||||||
|
|||||||
@@ -1,659 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"ruben/inventory2/internal/consts"
|
|
||||||
"ruben/inventory2/internal/logging"
|
|
||||||
"ruben/inventory2/internal/server/response"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Mux struct {
|
|
||||||
Mux *http.ServeMux
|
|
||||||
middleware []response.Middleware
|
|
||||||
log *logging.Logger
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrHandlerNotFound = fmt.Errorf("%w: handler not found", consts.ErrNotFound)
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMux(log *logging.Logger, ms ...response.Middleware) *Mux {
|
|
||||||
return &Mux{
|
|
||||||
Mux: http.NewServeMux(),
|
|
||||||
middleware: ms,
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) AddMiddleware(ms ...response.Middleware) *Mux {
|
|
||||||
m.middleware = append(m.middleware, ms...)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) Handle(pattern string, fn response.HandlerFunc) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "Handle",
|
|
||||||
"patter", pattern,
|
|
||||||
"fn", fn,
|
|
||||||
)
|
|
||||||
defer log.DebugCallf("called")()
|
|
||||||
m.Mux.Handle(pattern, response.Handler(m.applyMiddleware(fn)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) applyMiddleware(fn response.HandlerFunc) response.HandlerFunc {
|
|
||||||
return applyMiddleware(fn, m.middleware...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
m.Mux.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support for methods?
|
|
||||||
// TODO: strip the prefix from the call to the subrouter handler call
|
|
||||||
// Route does not accept methods or ... wildcards
|
|
||||||
// func (m *Mux) Route(basePathPattern string, sr Subrouter) {
|
|
||||||
func (m *Mux) Route(pattern string, sr Subrouter) {
|
|
||||||
log := m.log.With("method", "Route", "pattern", pattern, "sr", sr)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// ---
|
|
||||||
method, segments, _ := getHTTPMethodAndPathSegments(m.log, pattern)
|
|
||||||
|
|
||||||
if pattern == "" || (len(segments) == 0 && pattern[len(pattern)-1] != '/') {
|
|
||||||
panic("invalid subpath: " + pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
numPatternSegments := len(segments)
|
|
||||||
|
|
||||||
handler := response.Handler(m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr)))
|
|
||||||
|
|
||||||
cleanPattern := "/"
|
|
||||||
if joinedSegments := strings.Join(segments, "/"); joinedSegments != "" {
|
|
||||||
cleanPattern = "/" + joinedSegments + "/"
|
|
||||||
}
|
|
||||||
if method != "" {
|
|
||||||
cleanPattern = method + " " + cleanPattern
|
|
||||||
}
|
|
||||||
log.Debugf("cleanPattern: %s", cleanPattern)
|
|
||||||
// ---
|
|
||||||
|
|
||||||
/*
|
|
||||||
trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
||||||
if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
||||||
panic("invalid subpath: " + basePathPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
var trimmedPatternSegments []string
|
|
||||||
if trimmedSubpathPattern != "" {
|
|
||||||
trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
||||||
}
|
|
||||||
numPatternSegments := len(trimmedPatternSegments)
|
|
||||||
|
|
||||||
handler := response.Handler(m.applyMiddleware(buildSubrouterHandlerFunc(numPatternSegments, sr)))
|
|
||||||
|
|
||||||
cleanPattern := "/"
|
|
||||||
if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
||||||
cleanPattern = "/" + joinedSegments + "/"
|
|
||||||
}
|
|
||||||
fmt.Println("Mux.Route: cleanPattern:", cleanPattern)
|
|
||||||
*/
|
|
||||||
|
|
||||||
if method != "" {
|
|
||||||
m.Mux.Handle(method+" "+cleanPattern, handler)
|
|
||||||
} else {
|
|
||||||
m.Mux.Handle("GET "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("POST "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
Subrouter interface {
|
|
||||||
Handler(r *http.Request) (fn response.HandlerFunc, pathParams map[string]string, found bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
SubMux struct {
|
|
||||||
tree *muxTree
|
|
||||||
middleware []response.Middleware
|
|
||||||
log *logging.Logger
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewSubMux(log *logging.Logger, ms ...response.Middleware) *SubMux {
|
|
||||||
return &SubMux{
|
|
||||||
tree: newMuxTree(log.WithGroup("muxTree")),
|
|
||||||
middleware: ms,
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this is capturing all subroutes!
|
|
||||||
func (m *SubMux) Handle(pattern string, fn response.HandlerFunc) {
|
|
||||||
defer m.log.With("pattern", pattern, "fn", fn).DebugCallf("Handle")()
|
|
||||||
m.tree.set(pattern, m.applyMiddleware(fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: need http method support
|
|
||||||
// Route does not accept methods or ... wildcards
|
|
||||||
// func (m *SubMux) Route(basePathPattern string, sr Subrouter) {
|
|
||||||
func (m *SubMux) Route(pattern string, sr Subrouter) {
|
|
||||||
log := m.log.With("method", "Route", "pattern", pattern, "sr", sr)
|
|
||||||
defer log.DebugCallf("Route")()
|
|
||||||
method, segments, _ := getHTTPMethodAndPathSegments(m.log, pattern)
|
|
||||||
|
|
||||||
if pattern == "" || (len(segments) == 0 && pattern[len(pattern)-1] != '/') {
|
|
||||||
panic("invalid subpath: " + pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
numPatternSegments := len(segments)
|
|
||||||
|
|
||||||
handler := m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr))
|
|
||||||
|
|
||||||
cleanPattern := "/"
|
|
||||||
if joinedSegments := strings.Join(segments, "/"); joinedSegments != "" {
|
|
||||||
cleanPattern = "/" + joinedSegments + "/"
|
|
||||||
}
|
|
||||||
cleanPattern = path.Clean(cleanPattern)
|
|
||||||
if method != "" {
|
|
||||||
cleanPattern = method + " " + cleanPattern
|
|
||||||
}
|
|
||||||
log.Debugf("Handle about to be called: cleanPattern: %s", cleanPattern)
|
|
||||||
// ---
|
|
||||||
|
|
||||||
/*
|
|
||||||
// ---
|
|
||||||
trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
||||||
if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
||||||
panic("invalid subpath: " + basePathPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
var trimmedPatternSegments []string
|
|
||||||
if trimmedSubpathPattern != "" {
|
|
||||||
trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
||||||
}
|
|
||||||
numPatternSegments := len(trimmedPatternSegments)
|
|
||||||
|
|
||||||
handler := m.applyMiddleware(buildSubrouterHandlerFunc(m.log, numPatternSegments, sr))
|
|
||||||
|
|
||||||
// TODO: we're wrapping the pattern method!
|
|
||||||
cleanPattern := "/"
|
|
||||||
if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
||||||
cleanPattern = "/" + joinedSegments + "/"
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
m.Handle(cleanPattern, handler)
|
|
||||||
|
|
||||||
/*
|
|
||||||
m.Mux.Handle("GET "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("POST "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
||||||
m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Route does not accept methods or ... wildcards
|
|
||||||
// func (m *SubMux) Route(basePathPattern string, sr Subrouter) {
|
|
||||||
// trimmedSubpathPattern := strings.Trim(path.Clean(basePathPattern), "/")
|
|
||||||
// if trimmedSubpathPattern == "" && basePathPattern != "/" {
|
|
||||||
// panic("invalid subpath: " + basePathPattern)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var trimmedPatternSegments []string
|
|
||||||
// if trimmedSubpathPattern != "" {
|
|
||||||
// trimmedPatternSegments = strings.Split(trimmedSubpathPattern, "/")
|
|
||||||
// }
|
|
||||||
// numPatternSegments := len(trimmedPatternSegments)
|
|
||||||
//
|
|
||||||
// handler := m.applyMiddleware(buildSubrouterHandlerFunc(numPatternSegments, sr))
|
|
||||||
//
|
|
||||||
// // TODO: we're wrapping the pattern method!
|
|
||||||
// cleanPattern := "/"
|
|
||||||
// if joinedSegments := strings.Join(trimmedPatternSegments, "/"); joinedSegments != "" {
|
|
||||||
// cleanPattern = "/" + joinedSegments + "/"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// m.Handle(cleanPattern, handler)
|
|
||||||
//
|
|
||||||
// /*
|
|
||||||
// m.Mux.Handle("GET "+cleanPattern, handler)
|
|
||||||
// m.Mux.Handle("POST "+cleanPattern, handler)
|
|
||||||
// m.Mux.Handle("PUT "+cleanPattern, handler)
|
|
||||||
// m.Mux.Handle("PATCH "+cleanPattern, handler)
|
|
||||||
// m.Mux.Handle("DELETE "+cleanPattern, handler)
|
|
||||||
// */
|
|
||||||
// }
|
|
||||||
|
|
||||||
func buildSubrouterHandlerFunc(logger *logging.Logger, numPatternSegments int, sr Subrouter) response.HandlerFunc {
|
|
||||||
log := logger.With(
|
|
||||||
"function", "buildSubrouterHandlerFunc",
|
|
||||||
"numPatternSegments", numPatternSegments,
|
|
||||||
)
|
|
||||||
|
|
||||||
return func(r *http.Request) (res response.Response, err error) {
|
|
||||||
log := log.With("r.URL", r.URL)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"res", res,
|
|
||||||
"err", err,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fullPath := r.URL.Path
|
|
||||||
|
|
||||||
// set trailing path on request
|
|
||||||
|
|
||||||
segments, trailingSlash := getPathSegments(logger, r.URL.Path)
|
|
||||||
|
|
||||||
/*
|
|
||||||
endsInSlash := strings.HasSuffix(r.URL.Path, "/")
|
|
||||||
segments := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
|
||||||
*/
|
|
||||||
tail := segments[numPatternSegments:]
|
|
||||||
tailStr := "/"
|
|
||||||
if len(tail) > 0 {
|
|
||||||
tailStr += strings.Join(tail, "/")
|
|
||||||
if trailingSlash {
|
|
||||||
tailStr += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL.Path = tailStr
|
|
||||||
defer func() {
|
|
||||||
// reset the request path
|
|
||||||
r.URL.Path = fullPath
|
|
||||||
}()
|
|
||||||
|
|
||||||
// do the request
|
|
||||||
|
|
||||||
log.Debugf("handler called: %s", r.URL)
|
|
||||||
fn, params, ok := sr.Handler(r)
|
|
||||||
log.Debugf("handler returned: url = %s, fn = %v, params = %v, ok = %v", r.URL, fn, params, ok)
|
|
||||||
if !ok {
|
|
||||||
return nil, response.NotFound().Wrap(ErrHandlerNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
r.SetPathValue(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err = fn(r)
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SubMux) Handler(r *http.Request) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "Handler",
|
|
||||||
"r.URL", r.URL,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"fn", fn,
|
|
||||||
"params", params,
|
|
||||||
"found", found,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return m.tree.get(r.Method, r.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SubMux) applyMiddleware(fn response.HandlerFunc) response.HandlerFunc {
|
|
||||||
return applyMiddleware(fn, m.middleware...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// path pattern matching tree implementation
|
|
||||||
|
|
||||||
type (
|
|
||||||
muxTree struct {
|
|
||||||
log *logging.Logger
|
|
||||||
|
|
||||||
branches map[string]*muxTree
|
|
||||||
wildcardKey string
|
|
||||||
wildcardBranch *muxTree
|
|
||||||
|
|
||||||
method string
|
|
||||||
handler response.HandlerFunc
|
|
||||||
subrouteHandler response.HandlerFunc
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newMuxTree(log *logging.Logger) *muxTree {
|
|
||||||
return &muxTree{
|
|
||||||
log: log,
|
|
||||||
branches: make(map[string]*muxTree),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxTree) String() string {
|
|
||||||
if m == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
|
|
||||||
kvs := make([]string, 0, 6)
|
|
||||||
|
|
||||||
if m.subrouteHandler != nil {
|
|
||||||
kvs = append(kvs, fmt.Sprintf("subrouteHandler: %v", m.subrouteHandler))
|
|
||||||
}
|
|
||||||
if m.method != "" {
|
|
||||||
kvs = append(kvs, fmt.Sprintf("method: %v", m.method))
|
|
||||||
}
|
|
||||||
if m.handler != nil {
|
|
||||||
kvs = append(kvs, fmt.Sprintf("handler: %v", m.handler))
|
|
||||||
}
|
|
||||||
if m.wildcardKey != "" {
|
|
||||||
kvs = append(kvs, fmt.Sprintf("wildcardKey: %v", m.wildcardKey))
|
|
||||||
}
|
|
||||||
if m.wildcardBranch != nil {
|
|
||||||
kvs = append(kvs, fmt.Sprintf("wildcardBranch: %v", m.wildcardBranch))
|
|
||||||
}
|
|
||||||
if len(m.branches) > 0 {
|
|
||||||
branchKvs := make([]string, 0, len(m.branches))
|
|
||||||
for k, v := range m.branches {
|
|
||||||
branchKvs = append(branchKvs, fmt.Sprintf("%q: %s", k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.Sort(branchKvs)
|
|
||||||
|
|
||||||
kvs = append(kvs, fmt.Sprintf("branches: {%s}", strings.Join(branchKvs, ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxTree) set(pattern string, fn response.HandlerFunc) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "set",
|
|
||||||
"patter", pattern,
|
|
||||||
"fn", fn,
|
|
||||||
)
|
|
||||||
defer log.DebugCallf("called")()
|
|
||||||
method, segments, trailingSlash, endOfURLWildcard := getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard(m.log, pattern)
|
|
||||||
m.setBySegments(
|
|
||||||
method,
|
|
||||||
segments,
|
|
||||||
trailingSlash,
|
|
||||||
endOfURLWildcard,
|
|
||||||
fn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle /{$} properly
|
|
||||||
func (m *muxTree) setBySegments(method string, segments []string, trailingSlash, endOfURLWildcard bool, fn response.HandlerFunc) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "setBySegments",
|
|
||||||
"arg.method", method,
|
|
||||||
"segments", segments,
|
|
||||||
"trailingSlash", trailingSlash,
|
|
||||||
"endOfURLWildcard", endOfURLWildcard,
|
|
||||||
"fn", fn,
|
|
||||||
)
|
|
||||||
defer log.DebugCallf("called")()
|
|
||||||
if len(segments) == 0 {
|
|
||||||
m.method = method
|
|
||||||
if !trailingSlash || endOfURLWildcard {
|
|
||||||
m.handler = fn
|
|
||||||
} else if trailingSlash {
|
|
||||||
m.subrouteHandler = fn
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
head := segments[0]
|
|
||||||
tail := segments[1:]
|
|
||||||
|
|
||||||
var sub *muxTree
|
|
||||||
if key, ok := getWildcardPathSegmentKey(head); ok {
|
|
||||||
m.wildcardKey = key
|
|
||||||
if sub = m.wildcardBranch; sub == nil {
|
|
||||||
sub = newMuxTree(m.log)
|
|
||||||
m.wildcardBranch = sub
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if sub = m.branches[head]; sub == nil {
|
|
||||||
sub = newMuxTree(m.log)
|
|
||||||
m.branches[head] = sub
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if isWildcard, wildcardKey := isWildcardPathSegment(head); wildcardKey == "$" {
|
|
||||||
// TODO: test
|
|
||||||
// TODO: handle trailing end path matching
|
|
||||||
if len(tail) > 0 || trailingSlash {
|
|
||||||
panic("{$} wildcard applied outside of end of path")
|
|
||||||
}
|
|
||||||
m.method = method
|
|
||||||
m.handler = fn
|
|
||||||
return
|
|
||||||
} else if isWildcard {
|
|
||||||
m.wildcardKey = wildcardKey
|
|
||||||
if sub = m.wildcardBranch; sub == nil {
|
|
||||||
sub = newMuxTree(m.log)
|
|
||||||
m.wildcardBranch = sub
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if sub = m.branches[head]; sub == nil {
|
|
||||||
sub = newMuxTree(m.log)
|
|
||||||
m.branches[head] = sub
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub.setBySegments(method, tail, trailingSlash, endOfURLWildcard, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxTree) get(method, pattern string) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "get",
|
|
||||||
"arg.method", method,
|
|
||||||
"pattern", pattern,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"fn", fn,
|
|
||||||
"params", params,
|
|
||||||
"found", found,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
segments, trailingSlash := getPathSegments(m.log, pattern)
|
|
||||||
return m.getByHTTPMethodAndSegments(method, segments, trailingSlash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *muxTree) getByHTTPMethodAndSegments(method string, segments []string, trailingSlash bool) (fn response.HandlerFunc, params map[string]string, found bool) {
|
|
||||||
log := m.log.With(
|
|
||||||
"method", "getByHTTPMethodAndSegments",
|
|
||||||
"arg.method", method,
|
|
||||||
"segments", segments,
|
|
||||||
"trailingSlash", trailingSlash,
|
|
||||||
"muxTree", m,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"fn", fn,
|
|
||||||
"params", params,
|
|
||||||
"found", found,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if len(segments) == 0 {
|
|
||||||
log.Debugf("no segments")
|
|
||||||
if methodMatches := m.method == "" || m.method == method; methodMatches {
|
|
||||||
if fn = m.handler; fn == nil {
|
|
||||||
fn = m.subrouteHandler
|
|
||||||
}
|
|
||||||
return fn, map[string]string{}, fn != nil
|
|
||||||
}
|
|
||||||
return nil, map[string]string{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
head := segments[0]
|
|
||||||
tail := segments[1:]
|
|
||||||
|
|
||||||
if m.subrouteHandler != nil {
|
|
||||||
defer func() {
|
|
||||||
if !found && (m.method == "" || m.method == method) {
|
|
||||||
fn = m.subrouteHandler
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if sm, ok := m.branches[head]; ok {
|
|
||||||
log.Debugf("matching branch found")
|
|
||||||
//return sm.getByHTTPMethodAndSegments(method, tail, trailingSlash)
|
|
||||||
if fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash); found {
|
|
||||||
return fn, params, found
|
|
||||||
}
|
|
||||||
log.Debugf("matching branch mismatched at subpath")
|
|
||||||
} else {
|
|
||||||
log.Debugf("no matching branch found")
|
|
||||||
}
|
|
||||||
if sm := m.wildcardBranch; sm != nil {
|
|
||||||
wlog := log.With(
|
|
||||||
"wildcardKey", m.wildcardKey,
|
|
||||||
"sub.muxTree", sm,
|
|
||||||
)
|
|
||||||
wlog.Debugf("wildcard branch found")
|
|
||||||
//fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash)
|
|
||||||
if fn, params, found = sm.getByHTTPMethodAndSegments(method, tail, trailingSlash); found {
|
|
||||||
params[m.wildcardKey] = head
|
|
||||||
return fn, params, found
|
|
||||||
}
|
|
||||||
wlog.Debugf("wildcard branch mismatched at subpath")
|
|
||||||
} else {
|
|
||||||
log.Debugf("no wildcard branch found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- TODO: test ---
|
|
||||||
if m.subrouteHandler != nil && (m.method == method || m.method == "") {
|
|
||||||
log.Debugf("subroutes captured")
|
|
||||||
return m.subrouteHandler, nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("subroutes not captured")
|
|
||||||
// ---
|
|
||||||
|
|
||||||
return nil, map[string]string{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard(logger *logging.Logger, pattern string) (method string, segments []string, trailingSlash, endOfURLWildcard bool) {
|
|
||||||
log := logger.With(
|
|
||||||
"function", "getHTTPMethodAndPathSegmentsDroppingEndOfURLWildcard",
|
|
||||||
"pattern", pattern,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"method", method,
|
|
||||||
"segments", segments,
|
|
||||||
"trailingSlash", trailingSlash,
|
|
||||||
"endOfURLWildcard", endOfURLWildcard,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if vs := strings.SplitN(pattern, " ", 2); len(vs) == 2 {
|
|
||||||
method = vs[0]
|
|
||||||
pattern = vs[1]
|
|
||||||
}
|
|
||||||
segments, trailingSlash, endOfURLWildcard = getPathSegmentsWithoutEndOfURLWildcard(logger, pattern)
|
|
||||||
|
|
||||||
return method, segments, trailingSlash, endOfURLWildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHTTPMethodAndPathSegments(logger *logging.Logger, pattern string) (method string, segments []string, trailingSlash bool) {
|
|
||||||
log := logger.With(
|
|
||||||
"function", "getHTTPMethodAndPathSegments",
|
|
||||||
"pattern", pattern,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"method", method,
|
|
||||||
"segments", segments,
|
|
||||||
"trailingSlash", trailingSlash,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if vs := strings.SplitN(pattern, " ", 2); len(vs) == 2 {
|
|
||||||
method = vs[0]
|
|
||||||
pattern = vs[1]
|
|
||||||
}
|
|
||||||
segments, trailingSlash = getPathSegments(logger, pattern)
|
|
||||||
|
|
||||||
return method, segments, trailingSlash
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPathSegments(logger *logging.Logger, pathPattern string) (segments []string, trailingSlash bool) {
|
|
||||||
log := logger.With(
|
|
||||||
"function", "getPathSegments",
|
|
||||||
"pathPattern", pathPattern,
|
|
||||||
)
|
|
||||||
defer log.DebugCallf("getPathSegments")()
|
|
||||||
trailingSlash = strings.HasSuffix(pathPattern, "/")
|
|
||||||
|
|
||||||
p := strings.Trim(path.Clean(pathPattern), "/")
|
|
||||||
if p == "" {
|
|
||||||
return nil, trailingSlash
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Split(p, "/"), trailingSlash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: if endOfURLWildcard, then trailingSlash
|
|
||||||
func getPathSegmentsWithoutEndOfURLWildcard(logger *logging.Logger, pathPattern string) (segments []string, trailingSlash, endOfURLWildcard bool) {
|
|
||||||
log := logger.With(
|
|
||||||
"function", "getPathSegmentsWithoutEndOfURLWildcard",
|
|
||||||
"pathPattern", pathPattern,
|
|
||||||
)
|
|
||||||
defer log.DebugDeferf("called")(func() (string, []any) {
|
|
||||||
return "returned", []any{
|
|
||||||
"segments", segments,
|
|
||||||
"trailingSlash", trailingSlash,
|
|
||||||
"endOfURLWildcard", endOfURLWildcard,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
endOfURLWildcard = strings.HasSuffix(pathPattern, "/{$}")
|
|
||||||
if endOfURLWildcard {
|
|
||||||
pathPattern = pathPattern[:len(pathPattern)-3]
|
|
||||||
}
|
|
||||||
|
|
||||||
trailingSlash = strings.HasSuffix(pathPattern, "/")
|
|
||||||
|
|
||||||
p := strings.Trim(path.Clean(pathPattern), "/")
|
|
||||||
if p == "" {
|
|
||||||
return nil, trailingSlash, endOfURLWildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Split(p, "/"), trailingSlash, endOfURLWildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWildcardPathSegmentKey(s string) (string, bool) {
|
|
||||||
if isWildcard := len(s) > 2 && s[0] == '{' && s[len(s)-1] == '}'; isWildcard {
|
|
||||||
return s[1 : len(s)-1], true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyMiddleware(fn response.HandlerFunc, ms ...response.Middleware) response.HandlerFunc {
|
|
||||||
for _, mw := range slices.Backward(ms) {
|
|
||||||
prev := fn
|
|
||||||
fn = mw(func(r *http.Request) (response.Response, error) {
|
|
||||||
return prev(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove the consts. trace level
|
|
||||||
+49
-49
@@ -3,14 +3,15 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/angelbeltran/templater"
|
"github.com/angelbeltran/templater"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"ruben/inventory2/internal/domains/accounts"
|
"ruben/inventory2/internal/domains/accounts"
|
||||||
"ruben/inventory2/internal/domains/authentication"
|
"ruben/inventory2/internal/domains/authentication"
|
||||||
@@ -23,11 +24,11 @@ import (
|
|||||||
"ruben/inventory2/internal/server/api/webhooks"
|
"ruben/inventory2/internal/server/api/webhooks"
|
||||||
etsy_webhooks "ruben/inventory2/internal/server/api/webhooks/etsy"
|
etsy_webhooks "ruben/inventory2/internal/server/api/webhooks/etsy"
|
||||||
"ruben/inventory2/internal/server/middleware"
|
"ruben/inventory2/internal/server/middleware"
|
||||||
"ruben/inventory2/internal/server/response"
|
|
||||||
"ruben/inventory2/internal/server/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(
|
// r gin.IRouter, // TODO: use this everywhere
|
||||||
|
|
||||||
|
func Router(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *logging.Logger,
|
logger *logging.Logger,
|
||||||
contentDir string,
|
contentDir string,
|
||||||
@@ -35,11 +36,8 @@ func NewServer(
|
|||||||
accts *accounts.Store,
|
accts *accounts.Store,
|
||||||
etsy *etsy_platform.Platform,
|
etsy *etsy_platform.Platform,
|
||||||
auth *authentication.Authenticator,
|
auth *authentication.Authenticator,
|
||||||
) http.Handler {
|
) *gin.Engine {
|
||||||
mux := router.NewMux(
|
r := gin.Default()
|
||||||
logger,
|
|
||||||
middleware.LogRequests(ctx, logger.WithGroup("request")),
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: shouldn't this ACTUALLY be a middleware?
|
// TODO: shouldn't this ACTUALLY be a middleware?
|
||||||
// - only try to make this an actual middleware AFTER all the routers are broken out, so that way how the middleware is supposed to work can be known
|
// - only try to make this an actual middleware AFTER all the routers are broken out, so that way how the middleware is supposed to work can be known
|
||||||
@@ -54,22 +52,26 @@ func NewServer(
|
|||||||
|
|
||||||
// non-html content: scripts, styles, images, etc
|
// non-html content: scripts, styles, images, etc
|
||||||
|
|
||||||
scfs := http.FileServer(http.Dir(contentDir + "/scripts"))
|
r.Use(fileServer("/scripts", contentDir+"/scripts", func(c *gin.Context) {
|
||||||
mux.Mux.Handle("GET /scripts/", http.StripPrefix("/scripts", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
w := c.Writer
|
||||||
w.Header().Set("Content-Type", "text/javascript")
|
w.Header().Set("Content-Type", "text/javascript")
|
||||||
if path.Ext(r.URL.Path) == ".gz" {
|
if path.Ext(c.Request.URL.Path) == ".gz" {
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
}
|
}
|
||||||
scfs.ServeHTTP(w, r)
|
}))
|
||||||
})))
|
r.Static("/styles", "./styles")
|
||||||
mux.Mux.Handle("GET /styles/", http.StripPrefix("/styles", http.FileServer(http.Dir(contentDir+"/styles"))))
|
r.Static("/favicon", "./favicon")
|
||||||
mux.Mux.Handle("GET /favicon/", http.StripPrefix("/favicon", http.FileServer(http.Dir(contentDir+"/favicon"))))
|
|
||||||
|
|
||||||
// html (must be before the api endpoints, because the webpage middleware has to be installed beforehand
|
// html (must be before the api endpoints, because the webpage middleware has to be installed beforehand
|
||||||
|
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
c.Redirect(http.StatusMovedPermanently, "/ui")
|
||||||
|
})
|
||||||
|
|
||||||
// kind of a dumb way to capture routes for webpages
|
// kind of a dumb way to capture routes for webpages
|
||||||
wpr := templates_api.NewWebpageRouter(
|
templates_api.SetupRoutes(
|
||||||
logger.WithGroup("templates"),
|
logger.WithGroup("templates"),
|
||||||
|
r.Group("/ui"),
|
||||||
contentDir,
|
contentDir,
|
||||||
templater.NewTemplater(
|
templater.NewTemplater(
|
||||||
contentDir+"/templates",
|
contentDir+"/templates",
|
||||||
@@ -125,42 +127,27 @@ func NewServer(
|
|||||||
etsy,
|
etsy,
|
||||||
authMiddleware,
|
authMiddleware,
|
||||||
)
|
)
|
||||||
mux.AddMiddleware(
|
|
||||||
func(fn response.HandlerFunc) response.HandlerFunc {
|
|
||||||
return func(r *http.Request) (response.Response, error) {
|
|
||||||
res, err := fn(r)
|
|
||||||
if err == nil || !errors.Is(err, router.ErrHandlerNotFound) {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
renderWebPage, pathParams, found := wpr.Handler(r)
|
|
||||||
if !found {
|
|
||||||
return nil, response.NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range pathParams {
|
|
||||||
r.SetPathValue(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderWebPage(r)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
mux.Route("/", wpr)
|
|
||||||
|
|
||||||
// api endpoints
|
// api endpoints
|
||||||
|
|
||||||
mux.Route("/auth", auth_api.NewLoginSubrouter(logger.WithGroup("/auth"), auth))
|
api := r.Group("/api")
|
||||||
mux.Route("/accounts", accounts_api.NewAccountSubrouter(
|
|
||||||
|
auth_api.Routes(
|
||||||
|
api.Group("/auth"),
|
||||||
|
logger.WithGroup("/auth"),
|
||||||
|
auth,
|
||||||
|
)
|
||||||
|
accounts_api.Routes(
|
||||||
|
api.Group("/accounts"),
|
||||||
logger.WithGroup("/accounts"),
|
logger.WithGroup("/accounts"),
|
||||||
accts,
|
accts,
|
||||||
authMiddleware,
|
authMiddleware,
|
||||||
))
|
)
|
||||||
|
|
||||||
// api webhooks (TODO: make a router for these)
|
// api webhooks (TODO: make a router for these)
|
||||||
|
|
||||||
webhookHandler := http.StripPrefix("/webhooks", webhooks.New(
|
webhooks.Webhooks(
|
||||||
|
api.Group("/webhooks"),
|
||||||
logger.WithGroup("webhooks"),
|
logger.WithGroup("webhooks"),
|
||||||
rawEvents,
|
rawEvents,
|
||||||
etsy,
|
etsy,
|
||||||
@@ -169,10 +156,23 @@ func NewServer(
|
|||||||
OAuthRedirectURIWithAcctIDParam: "/oauth/account/{acctID}/auth_code",
|
OAuthRedirectURIWithAcctIDParam: "/oauth/account/{acctID}/auth_code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
))
|
)
|
||||||
mux.Mux.Handle("GET /webhooks/", webhookHandler)
|
|
||||||
mux.Mux.Handle("POST /webhooks/", webhookHandler)
|
|
||||||
mux.Mux.Handle("PUT /webhooks/", webhookHandler)
|
|
||||||
|
|
||||||
return mux
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileServer(urlPrefix, dir string, beforeServe func(c *gin.Context)) gin.HandlerFunc {
|
||||||
|
scfs := http.StripPrefix(urlPrefix, http.FileServer(http.Dir(dir)))
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
r := c.Request
|
||||||
|
if r.URL.Path == urlPrefix || strings.HasPrefix(r.URL.Path, path.Join(urlPrefix, "/")) {
|
||||||
|
if beforeServe != nil {
|
||||||
|
beforeServe(c)
|
||||||
|
}
|
||||||
|
scfs.ServeHTTP(c.Writer, r)
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need a c.Next()?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,24 +50,6 @@ func main() {
|
|||||||
// Time format (Default: time.StampMilli)
|
// Time format (Default: time.StampMilli)
|
||||||
//TimeFormat: "",
|
//TimeFormat: "",
|
||||||
}))
|
}))
|
||||||
/*
|
|
||||||
logger := slog.New(tint.NewHandler(os.Stderr, &tint.Options{
|
|
||||||
AddSource: true,
|
|
||||||
Level: slog.LevelDebug,
|
|
||||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
|
||||||
// this can perform general key=value log cleanup
|
|
||||||
|
|
||||||
if a.Key == slog.SourceKey && len(groups) == 0 {
|
|
||||||
source := a.Value.Any().(*slog.Source)
|
|
||||||
source.File = strings.TrimPrefix(source.File, "/home/angel/go/src/ruben/inventory2/internal")
|
|
||||||
}
|
|
||||||
|
|
||||||
return a
|
|
||||||
},
|
|
||||||
// Time format (Default: time.StampMilli)
|
|
||||||
//TimeFormat: "",
|
|
||||||
}))
|
|
||||||
*/
|
|
||||||
|
|
||||||
logger.Info("application starting")
|
logger.Info("application starting")
|
||||||
|
|
||||||
@@ -168,25 +150,27 @@ func runAuthProcesses(ctx context.Context, auth *authentication.Authenticator) <
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runServer(ctx context.Context, logger *logging.Logger, connPool *pgxpool.Pool, auth *authentication.Authenticator) <-chan error {
|
func runServer(ctx context.Context, logger *logging.Logger, connPool *pgxpool.Pool, auth *authentication.Authenticator) <-chan error {
|
||||||
srv := &http.Server{
|
r := server.Router(
|
||||||
Addr: ":8082", // local
|
ctx,
|
||||||
Handler: server.NewServer(
|
logger.WithGroup("server"),
|
||||||
ctx,
|
"./",
|
||||||
logger.WithGroup("server"),
|
raw_events.NewStore(logger.WithGroup("raw-event-store"), connPool),
|
||||||
"./",
|
accounts.NewStore(logger, connPool),
|
||||||
raw_events.NewStore(logger.WithGroup("raw-event-store"), connPool),
|
etsy_platform.NewPlatform(
|
||||||
accounts.NewStore(logger, connPool),
|
logger,
|
||||||
etsy_platform.NewPlatform(
|
func(acctID int64) string {
|
||||||
logger,
|
return fmt.Sprintf("/oauth/account/%d/auth_code", acctID)
|
||||||
func(acctID int64) string {
|
},
|
||||||
return fmt.Sprintf("/oauth/account/%d/auth_code", acctID)
|
etsyAPIKeystring,
|
||||||
},
|
etsyAPISharedSecret,
|
||||||
etsyAPIKeystring,
|
connPool,
|
||||||
etsyAPISharedSecret,
|
|
||||||
connPool,
|
|
||||||
),
|
|
||||||
auth,
|
|
||||||
),
|
),
|
||||||
|
auth,
|
||||||
|
)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: ":8082", // local
|
||||||
|
Handler: r,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|||||||
+3
-3
@@ -33,7 +33,7 @@
|
|||||||
{{- $shops := $dot.Accounts.GetShops $acctID -}}
|
{{- $shops := $dot.Accounts.GetShops $acctID -}}
|
||||||
<select
|
<select
|
||||||
class="min-w-fit cursor-pointer"
|
class="min-w-fit cursor-pointer"
|
||||||
hx-put="/accounts/{{$acctID}}/inventory/sync-groups/draft/listings/{{$orderIndex}}/shop"
|
hx-put="/api/accounts/{{$acctID}}/inventory/sync-groups/draft/listings/{{$orderIndex}}/shop"
|
||||||
hx-vals='js:{
|
hx-vals='js:{
|
||||||
platform: event.target.value.replace(/-[^ ]*/, ""),
|
platform: event.target.value.replace(/-[^ ]*/, ""),
|
||||||
"shop-id": event.target.value.replace(/[^ ]*-/, "")
|
"shop-id": event.target.value.replace(/[^ ]*-/, "")
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<select
|
<select
|
||||||
class="min-w-fit cursor-pointer"
|
class="min-w-fit cursor-pointer"
|
||||||
|
|
||||||
hx-put="/accounts/{{$acctID}}/inventory/sync-groups/draft/listings/{{$orderIndex}}/listing"
|
hx-put="/api/accounts/{{$acctID}}/inventory/sync-groups/draft/listings/{{$orderIndex}}/listing"
|
||||||
hx-vals='js:{
|
hx-vals='js:{
|
||||||
"listing-id": event.target.value,
|
"listing-id": event.target.value,
|
||||||
}'
|
}'
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
|
|
||||||
<td data-id="remove">
|
<td data-id="remove">
|
||||||
{{ componentBody "button-dev"
|
{{ componentBody "button-dev"
|
||||||
"HXDelete" (printf "/accounts/%d/inventory/sync-groups/draft/listings/%d" $acctID $orderIndex)
|
"HXDelete" (printf "/api/accounts/%d/inventory/sync-groups/draft/listings/%d" $acctID $orderIndex)
|
||||||
"HXTarget" "closest tr"
|
"HXTarget" "closest tr"
|
||||||
"HXSwap" "outerHTML"
|
"HXSwap" "outerHTML"
|
||||||
"Text" "Remove"
|
"Text" "Remove"
|
||||||
|
|||||||
@@ -59,9 +59,9 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{- $listings := $dot.Accounts.GetSyncGroupListingDrafts $acctID }}
|
{{- $listings := $dot.Accounts.GetSyncGroupListingDrafts $acctID }}
|
||||||
{{- range $i, $listing := $listings }}
|
{{- range $listing := $listings }}
|
||||||
{{- componentBody "accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}"
|
{{- componentBody "accounts/{acctID}/inventory/sync-groups/draft/listings/{orderIndex}"
|
||||||
"dot" ($dot | addPathParam "orderIndex" $i)
|
"dot" ($dot | addPathParam "orderIndex" $listing.OrderIndex)
|
||||||
}}
|
}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
>
|
>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="/">
|
<a href="/ui">
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
{{- if not $userID }}
|
{{- if not $userID }}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/login">
|
<a href="/api/auth/login">
|
||||||
Log In / Sign Up
|
Log In / Sign Up
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -41,25 +41,25 @@
|
|||||||
{{- else if $acctID }}
|
{{- else if $acctID }}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/accounts/{{$acctID}}">
|
<a href="/ui/accounts/{{$acctID}}">
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/accounts/{{$acctID}}/reports">
|
<a href="/ui/accounts/{{$acctID}}/reports">
|
||||||
Reports
|
Reports
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/accounts/{{$acctID}}/inventory">
|
<a href="/ui/accounts/{{$acctID}}/inventory">
|
||||||
Inventory
|
Inventory
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/logout">
|
<a href="/api/auth/logout">
|
||||||
Log Out
|
Log Out
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<ul class="flex text-nowrap overflow-x-scroll overflow-y-hidden">
|
<ul class="flex text-nowrap overflow-x-scroll overflow-y-hidden">
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/" class="p-[1em] font-bold {{ if eq $path "/" }}selected{{ end }}">
|
<a href="/ui" class="p-[1em] font-bold {{ if eq $path "/" }}selected{{ end }}">
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
{{- if not $userID }}
|
{{- if not $userID }}
|
||||||
|
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/auth/login" class="p-[1em] font-bold {{ if eq $path "/auth/login" }}selected{{ end }}">
|
<a href="/api/auth/login" class="p-[1em] font-bold {{ if eq $path "/auth/login" }}selected{{ end }}">
|
||||||
Log In / Sign Up
|
Log In / Sign Up
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -53,25 +53,25 @@
|
|||||||
{{- else if $acctID }}
|
{{- else if $acctID }}
|
||||||
|
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/accounts/{{$acctID}}" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d" $acctID) }}selected{{ end }}">
|
<a href="/ui/accounts/{{$acctID}}" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d" $acctID) }}selected{{ end }}">
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/accounts/{{$acctID}}/inventory" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d/inventory" $acctID) }}selected{{ end }}">
|
<a href="/ui/accounts/{{$acctID}}/inventory" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d/inventory" $acctID) }}selected{{ end }}">
|
||||||
Inventory
|
Inventory
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/accounts/{{$acctID}}/reports" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d/reports" $acctID) }}selected{{ end }}">
|
<a href="/ui/accounts/{{$acctID}}/reports" class="p-[1em] font-bold {{ if eq $path (printf "/accounts/%d/reports" $acctID) }}selected{{ end }}">
|
||||||
Reports
|
Reports
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="pt-[1em] pb-[1em]">
|
<li class="pt-[1em] pb-[1em]">
|
||||||
<a href="/auth/logout" class="p-[1em] font-bold">
|
<a href="/api/auth/logout" class="p-[1em] font-bold">
|
||||||
Log Out
|
Log Out
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -17,4 +17,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
<h2><a href="/accounts/{{$acctID.AccountID}}/reports">View Reports</a></h2>
|
<h2><a href="/ui/accounts/{{$acctID.AccountID}}/reports">View Reports</a></h2>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{ componentBody "button-dev"
|
{{ componentBody "button-dev"
|
||||||
"HXPost" (printf "/accounts/%d/inventory/sync-groups/draft/listings" $acctID)
|
"HXPost" (printf "/api/accounts/%d/inventory/sync-groups/draft/listings" $acctID)
|
||||||
"HXTarget" "previous table > tbody"
|
"HXTarget" "previous table > tbody"
|
||||||
"HXSwap" "beforeend"
|
"HXSwap" "beforeend"
|
||||||
"Class" "mt-[1em] mb-[1em]"
|
"Class" "mt-[1em] mb-[1em]"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
{{/* TODO: save the listings as a sync group on click */}}
|
{{/* TODO: save the listings as a sync group on click */}}
|
||||||
{{ componentBody "button-dev"
|
{{ componentBody "button-dev"
|
||||||
"ID" "create-sync-group-button"
|
"ID" "create-sync-group-button"
|
||||||
"HXPost" (printf "/accounts/%d/sync-groups" $acctID)
|
"HXPost" (printf "/api/accounts/%d/sync-groups" $acctID)
|
||||||
"Class" "mt-[1em] mb-[1em]"
|
"Class" "mt-[1em] mb-[1em]"
|
||||||
"Text" "Save Sync Group"
|
"Text" "Save Sync Group"
|
||||||
"Disabled" true
|
"Disabled" true
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<section class="w-full flex justify-center mt-[1em]">
|
<section class="w-full flex justify-center mt-[1em]">
|
||||||
{{ componentBody "button-dev"
|
{{ componentBody "button-dev"
|
||||||
"HXGet" (printf "/accounts/%d/inventory/sync-table/new-row" $acctID)
|
"HXGet" (printf "/api/accounts/%d/inventory/sync-table/new-row" $acctID)
|
||||||
"HXTarget" "#inventory-table > tbody"
|
"HXTarget" "#inventory-table > tbody"
|
||||||
"HXSwap" "beforeend"
|
"HXSwap" "beforeend"
|
||||||
"Class" "mt-[1em] mb-[1em]"
|
"Class" "mt-[1em] mb-[1em]"
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<a href="/">Return Home</a>
|
<a href="/ui">Return Home</a>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
{{- if and .Identity.User.UserID (not .Identity.Account) }}
|
{{- if and .Identity.User.UserID (not .Identity.Account) }}
|
||||||
{{/* TODO: make account creation automatic as part of the user creation process */}}
|
{{/* TODO: make account creation automatic as part of the user creation process */}}
|
||||||
<h2><a href="/account-creation">New Account</a></h2>
|
<h2><a href="/ui/account-creation">New Account</a></h2>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
<section class="flex flex-col items-center max-w-full mt-[1em] mb-[1em] gap-y-[2em]">
|
<section class="flex flex-col items-center max-w-full mt-[1em] mb-[1em] gap-y-[2em]">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="border-button border-sidebar-border bg-card text-lg text-center max-w-[50%]">
|
<div class="border-button border-sidebar-border bg-card text-lg text-center max-w-[50%]">
|
||||||
<a href="/login" class="block p-[1em] font-bold">
|
<a href="/ui/login" class="block p-[1em] font-bold">
|
||||||
Create an Account
|
Create an Account
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<a href="/">Return Home</a>
|
<a href="/ui">Return Home</a>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user