Deploy staging to prod #286

Merged
itzaname merged 23 commits from staging into master 2025-12-26 03:30:36 +00:00
102 changed files with 15133 additions and 2144 deletions

2
Cargo.lock generated
View File

@@ -2019,7 +2019,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "submissions-api"
version = "0.10.0"
version = "0.10.1"
dependencies = [
"chrono",
"reqwest",

45
go.mod
View File

@@ -11,17 +11,18 @@ require (
github.com/dchest/siphash v1.2.3
github.com/gin-gonic/gin v1.10.1
github.com/go-faster/errors v0.7.1
github.com/go-faster/jx v1.1.0
github.com/go-faster/jx v1.2.0
github.com/nats-io/nats.go v1.37.0
github.com/ogen-go/ogen v1.2.1
github.com/ogen-go/ogen v1.18.0
github.com/redis/go-redis/v9 v9.10.0
github.com/sirupsen/logrus v1.9.3
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.6
github.com/urfave/cli/v2 v2.27.6
go.opentelemetry.io/otel v1.32.0
go.opentelemetry.io/otel/metric v1.32.0
go.opentelemetry.io/otel/trace v1.32.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/metric v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
google.golang.org/grpc v1.48.0
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.25.12
@@ -33,9 +34,11 @@ require (
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -55,7 +58,7 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
@@ -65,36 +68,38 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-faster/yaml v0.4.6 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
// github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.23.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/net v0.48.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
gopkg.in/yaml.v2 v2.4.0 // indirect
)

69
go.sum
View File

@@ -14,12 +14,18 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
@@ -39,8 +45,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -49,6 +59,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -63,11 +75,13 @@ github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AY
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg=
github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg=
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -113,8 +127,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/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.5.6/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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -140,6 +154,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -159,6 +175,8 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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=
@@ -176,11 +194,15 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/ogen-go/ogen v1.2.1 h1:C5A0lvUMu2wl+eWIxnpXMWnuOJ26a2FyzR1CIC2qG0M=
github.com/ogen-go/ogen v1.2.1/go.mod h1:P2zQdEu8UqaVRfD5GEFvl+9q63VjMLvDquq1wVbyInM=
github.com/ogen-go/ogen v1.18.0 h1:6RQ7lFBjOeNaUWu4getfqIh4GJbEY4hqKuzDtec/g60=
github.com/ogen-go/ogen v1.18.0/go.mod h1:dHFr2Wf6cA7tSxMI+zPC21UR5hAlDw8ZYUkK3PziURY=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
@@ -188,6 +210,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -204,8 +230,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
@@ -221,12 +248,14 @@ github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -234,6 +263,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -242,15 +273,21 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -266,6 +303,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -275,6 +314,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -293,6 +334,8 @@ 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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -303,6 +346,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -312,6 +357,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -14,15 +14,41 @@ tags:
description: Long-running operations
- name: Session
description: Session queries
- name: Stats
description: Statistics queries
- name: Submissions
description: Submission operations
- name: Scripts
description: Script operations
- name: ScriptPolicy
description: Script policy operations
- name: Thumbnails
description: Thumbnail operations
- name: Users
description: User operations
security:
- cookieAuth: []
paths:
/stats:
get:
summary: Get aggregate statistics
operationId: getStats
tags:
- Stats
security: []
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Stats"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/session/user:
get:
summary: Get information about the currently logged in user
@@ -1438,6 +1464,222 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
/thumbnails/assets:
post:
Quaternions marked this conversation as resolved
Review

Is thumbnails and usernames post because it mutates the cache? Just wondering why it's not a get request.

Is thumbnails and usernames post because it mutates the cache? Just wondering why it's not a get request.
Review

It's a batch request with a json body payload. You generally don't do body content on GET requests.

It's a batch request with a json body payload. You generally don't do body content on GET requests.
summary: Batch fetch asset thumbnails
operationId: batchAssetThumbnails
tags:
- Thumbnails
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- assetIds
properties:
assetIds:
type: array
items:
type: integer
format: uint64
maxItems: 100
description: Array of asset IDs (max 100)
size:
type: string
enum:
- "150x150"
- "420x420"
- "768x432"
default: "420x420"
description: Thumbnail size
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
thumbnails:
type: object
additionalProperties:
type: string
description: Map of asset ID to thumbnail URL
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/thumbnails/asset/{AssetID}:
get:
summary: Get single asset thumbnail
operationId: getAssetThumbnail
tags:
- Thumbnails
security: []
parameters:
- name: AssetID
in: path
required: true
schema:
type: integer
format: uint64
- name: size
in: query
schema:
type: string
enum:
- "150x150"
- "420x420"
- "768x432"
default: "420x420"
responses:
"302":
description: Redirect to thumbnail URL
headers:
Location:
description: URL to redirect to
schema:
type: string
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/thumbnails/users:
post:
summary: Batch fetch user avatar thumbnails
operationId: batchUserThumbnails
tags:
- Thumbnails
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- userIds
properties:
userIds:
type: array
items:
type: integer
format: uint64
maxItems: 100
description: Array of user IDs (max 100)
size:
type: string
enum:
- "150x150"
- "420x420"
- "768x432"
default: "150x150"
description: Thumbnail size
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
thumbnails:
type: object
additionalProperties:
type: string
description: Map of user ID to thumbnail URL
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/thumbnails/user/{UserID}:
get:
summary: Get single user avatar thumbnail
operationId: getUserThumbnail
tags:
- Thumbnails
security: []
parameters:
- name: UserID
in: path
required: true
schema:
type: integer
format: uint64
- name: size
in: query
schema:
type: string
enum:
- "150x150"
- "420x420"
- "768x432"
default: "150x150"
responses:
"302":
description: Redirect to thumbnail URL
headers:
Location:
description: URL to redirect to
schema:
type: string
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/usernames:
post:
summary: Batch fetch usernames
operationId: batchUsernames
tags:
- Users
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- userIds
properties:
userIds:
type: array
items:
type: integer
format: uint64
maxItems: 100
description: Array of user IDs (max 100)
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
usernames:
type: object
additionalProperties:
type: string
description: Map of user ID to username
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
securitySchemes:
cookieAuth:
@@ -2061,6 +2303,47 @@ components:
type: integer
format: int32
minimum: 0
Stats:
description: Aggregate statistics for submissions and mapfixes
type: object
properties:
TotalSubmissions:
type: integer
format: int64
minimum: 0
description: Total number of submissions
TotalMapfixes:
type: integer
format: int64
minimum: 0
description: Total number of mapfixes
ReleasedSubmissions:
type: integer
format: int64
minimum: 0
description: Number of released submissions
ReleasedMapfixes:
type: integer
format: int64
minimum: 0
description: Number of released mapfixes
SubmittedSubmissions:
type: integer
format: int64
minimum: 0
description: Number of submissions under review
SubmittedMapfixes:
type: integer
format: int64
minimum: 0
description: Number of mapfixes under review
required:
- TotalSubmissions
- TotalMapfixes
- ReleasedSubmissions
- ReleasedMapfixes
- SubmittedSubmissions
- SubmittedMapfixes
Error:
description: Represents error object
type: object

View File

@@ -5,14 +5,14 @@ package api
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/otelogen"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
var (
@@ -32,6 +32,7 @@ type otelConfig struct {
Tracer trace.Tracer
MeterProvider metric.MeterProvider
Meter metric.Meter
Attributes []attribute.KeyValue
}
func (cfg *otelConfig) initOTEL() {
@@ -215,6 +216,13 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
})
}
// WithAttributes specifies default otel attributes.
func WithAttributes(attributes ...attribute.KeyValue) Option {
return otelOptionFunc(func(cfg *otelConfig) {
cfg.Attributes = attributes
})
}
// WithClient specifies http client to use.
func WithClient(client ht.Client) ClientOption {
return optionFunc[clientConfig](func(cfg *clientConfig) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
// Code generated by ogen, DO NOT EDIT.
package api
// setDefaults set default value of fields.
func (s *BatchAssetThumbnailsReq) setDefaults() {
{
val := BatchAssetThumbnailsReqSize("420x420")
s.Size.SetTo(val)
}
}
// setDefaults set default value of fields.
func (s *BatchUserThumbnailsReq) setDefaults() {
{
val := BatchUserThumbnailsReqSize("150x150")
s.Size.SetTo(val)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,9 @@ const (
ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload"
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
BatchAssetThumbnailsOperation OperationName = "BatchAssetThumbnails"
BatchUserThumbnailsOperation OperationName = "BatchUserThumbnails"
BatchUsernamesOperation OperationName = "BatchUsernames"
CreateMapfixOperation OperationName = "CreateMapfix"
CreateMapfixAuditCommentOperation OperationName = "CreateMapfixAuditComment"
CreateScriptOperation OperationName = "CreateScript"
@@ -40,12 +43,15 @@ const (
DeleteScriptOperation OperationName = "DeleteScript"
DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy"
DownloadMapAssetOperation OperationName = "DownloadMapAsset"
GetAssetThumbnailOperation OperationName = "GetAssetThumbnail"
GetMapOperation OperationName = "GetMap"
GetMapfixOperation OperationName = "GetMapfix"
GetOperationOperation OperationName = "GetOperation"
GetScriptOperation OperationName = "GetScript"
GetScriptPolicyOperation OperationName = "GetScriptPolicy"
GetStatsOperation OperationName = "GetStats"
GetSubmissionOperation OperationName = "GetSubmission"
GetUserThumbnailOperation OperationName = "GetUserThumbnail"
ListMapfixAuditEventsOperation OperationName = "ListMapfixAuditEvents"
ListMapfixesOperation OperationName = "ListMapfixes"
ListMapsOperation OperationName = "ListMaps"

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
package api
import (
"bytes"
"fmt"
"io"
"mime"
@@ -10,13 +11,13 @@ import (
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate"
)
func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
req *MapfixTriggerCreate,
func (s *Server) decodeBatchAssetThumbnailsRequest(r *http.Request) (
req *BatchAssetThumbnailsReq,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -37,22 +38,266 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request BatchAssetThumbnailsReq
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, rawBody, close, nil
default:
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeBatchUserThumbnailsRequest(r *http.Request) (
req *BatchUserThumbnailsReq,
rawBody []byte,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request BatchUserThumbnailsReq
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, rawBody, close, nil
default:
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeBatchUsernamesRequest(r *http.Request) (
req *BatchUsernamesReq,
rawBody []byte,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request BatchUsernamesReq
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, rawBody, close, nil
default:
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
req *MapfixTriggerCreate,
rawBody []byte,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = errors.Join(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = errors.Join(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request MapfixTriggerCreate
@@ -70,7 +315,7 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -78,16 +323,17 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateMapfixAuditCommentRequest(r *http.Request) (
req CreateMapfixAuditCommentReq,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -108,20 +354,21 @@ func (s *Server) decodeCreateMapfixAuditCommentRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "text/plain":
reader := r.Body
request := CreateMapfixAuditCommentReq{Data: reader}
return request, close, nil
return request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateScriptRequest(r *http.Request) (
req *ScriptCreate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -142,22 +389,29 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request ScriptCreate
@@ -175,7 +429,7 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -183,16 +437,17 @@ func (s *Server) decodeCreateScriptRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
req *ScriptPolicyCreate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -213,22 +468,29 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request ScriptPolicyCreate
@@ -246,7 +508,7 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -254,16 +516,17 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
req *SubmissionTriggerCreate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -284,22 +547,29 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request SubmissionTriggerCreate
@@ -317,7 +587,7 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -325,16 +595,17 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
req *SubmissionTriggerCreate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -355,22 +626,29 @@ func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request SubmissionTriggerCreate
@@ -388,7 +666,7 @@ func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -396,16 +674,17 @@ func (s *Server) decodeCreateSubmissionAdminRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) (
req CreateSubmissionAuditCommentReq,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -426,20 +705,21 @@ func (s *Server) decodeCreateSubmissionAuditCommentRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "text/plain":
reader := r.Body
request := CreateSubmissionAuditCommentReq{Data: reader}
return request, close, nil
return request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
req []ReleaseInfo,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -460,22 +740,29 @@ func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request []ReleaseInfo
@@ -501,7 +788,7 @@ func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if request == nil {
@@ -534,16 +821,17 @@ func (s *Server) decodeReleaseSubmissionsRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return request, close, nil
return request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
req *ScriptUpdate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -564,22 +852,29 @@ func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request ScriptUpdate
@@ -597,7 +892,7 @@ func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -605,16 +900,17 @@ func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
req *ScriptPolicyUpdate,
rawBody []byte,
close func() error,
rerr error,
) {
@@ -635,22 +931,29 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
return req, rawBody, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
defer func() {
_ = r.Body.Close()
}()
if err != nil {
return req, close, err
return req, rawBody, close, err
}
// Reset the body to allow for downstream reading.
r.Body = io.NopCloser(bytes.NewBuffer(buf))
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
return req, rawBody, close, validate.ErrBodyRequired
}
rawBody = append(rawBody, buf...)
d := jx.DecodeBytes(buf)
var request ScriptPolicyUpdate
@@ -668,7 +971,7 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
Body: buf,
Err: err,
}
return req, close, err
return req, rawBody, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
@@ -676,10 +979,10 @@ func (s *Server) decodeUpdateScriptPolicyRequest(r *http.Request) (
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
return req, rawBody, close, errors.Wrap(err, "validate")
}
return &request, close, nil
return &request, rawBody, close, nil
default:
return req, close, validate.InvalidContentType(ct)
return req, rawBody, close, validate.InvalidContentType(ct)
}
}

View File

@@ -7,10 +7,51 @@ import (
"net/http"
"github.com/go-faster/jx"
ht "github.com/ogen-go/ogen/http"
)
func encodeBatchAssetThumbnailsRequest(
req *BatchAssetThumbnailsReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeBatchUserThumbnailsRequest(
req *BatchUserThumbnailsReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeBatchUsernamesRequest(
req *BatchUsernamesReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeCreateMapfixRequest(
req *MapfixTriggerCreate,
r *http.Request,

View File

@@ -11,8 +11,9 @@ import (
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/conv"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/uri"
"github.com/ogen-go/ogen/validate"
)
@@ -1456,6 +1457,282 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu
return res, errors.Wrap(defRes, "error")
}
func decodeBatchAssetThumbnailsResponse(resp *http.Response) (res *BatchAssetThumbnailsOK, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response BatchAssetThumbnailsOK
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeBatchUserThumbnailsResponse(resp *http.Response) (res *BatchUserThumbnailsOK, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response BatchUserThumbnailsOK
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeBatchUsernamesResponse(resp *http.Response) (res *BatchUsernamesOK, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response BatchUsernamesOK
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeCreateMapfixResponse(resp *http.Response) (res *OperationID, _ error) {
switch resp.StatusCode {
case 201:
@@ -2277,6 +2554,105 @@ func decodeDownloadMapAssetResponse(resp *http.Response) (res DownloadMapAssetOK
return res, errors.Wrap(defRes, "error")
}
func decodeGetAssetThumbnailResponse(resp *http.Response) (res *GetAssetThumbnailFound, _ error) {
switch resp.StatusCode {
case 302:
// Code 302.
var wrapper GetAssetThumbnailFound
h := uri.NewHeaderDecoder(resp.Header)
// Parse "Location" header.
{
cfg := uri.HeaderParameterDecodingConfig{
Name: "Location",
Explode: false,
}
if err := func() error {
if err := h.HasParam(cfg); err == nil {
if err := h.DecodeParam(cfg, func(d uri.Decoder) error {
var wrapperDotLocationVal string
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
wrapperDotLocationVal = c
return nil
}(); err != nil {
return err
}
wrapper.Location.SetTo(wrapperDotLocationVal)
return nil
}); err != nil {
return err
}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "parse Location header")
}
}
return &wrapper, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) {
switch resp.StatusCode {
case 200:
@@ -2782,6 +3158,107 @@ func decodeGetScriptPolicyResponse(resp *http.Response) (res *ScriptPolicy, _ er
return res, errors.Wrap(defRes, "error")
}
func decodeGetStatsResponse(resp *http.Response) (res *Stats, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Stats
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error) {
switch resp.StatusCode {
case 200:
@@ -2883,6 +3360,105 @@ func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error)
return res, errors.Wrap(defRes, "error")
}
func decodeGetUserThumbnailResponse(resp *http.Response) (res *GetUserThumbnailFound, _ error) {
switch resp.StatusCode {
case 302:
// Code 302.
var wrapper GetUserThumbnailFound
h := uri.NewHeaderDecoder(resp.Header)
// Parse "Location" header.
{
cfg := uri.HeaderParameterDecodingConfig{
Name: "Location",
Explode: false,
}
if err := func() error {
if err := h.HasParam(cfg); err == nil {
if err := h.DecodeParam(cfg, func(d uri.Decoder) error {
var wrapperDotLocationVal string
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
wrapperDotLocationVal = c
return nil
}(); err != nil {
return err
}
wrapper.Location.SetTo(wrapperDotLocationVal)
return nil
}); err != nil {
return err
}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "parse Location header")
}
}
return &wrapper, nil
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if err := response.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeListMapfixAuditEventsResponse(resp *http.Response) (res []AuditEvent, _ error) {
switch resp.StatusCode {
case 200:

View File

@@ -8,10 +8,11 @@ import (
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/conv"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/uri"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
)
func encodeActionMapfixAcceptedResponse(response *ActionMapfixAcceptedNoContent, w http.ResponseWriter, span trace.Span) error {
@@ -182,6 +183,48 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated
return nil
}
func encodeBatchAssetThumbnailsResponse(response *BatchAssetThumbnailsOK, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeBatchUserThumbnailsResponse(response *BatchUserThumbnailsOK, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeBatchUsernamesResponse(response *BatchUsernamesOK, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(201)
@@ -296,6 +339,32 @@ func encodeDownloadMapAssetResponse(response DownloadMapAssetOK, w http.Response
return nil
}
func encodeGetAssetThumbnailResponse(response *GetAssetThumbnailFound, w http.ResponseWriter, span trace.Span) error {
// Encoding response headers.
{
h := uri.NewHeaderEncoder(w.Header())
// Encode "Location" header.
{
cfg := uri.HeaderParameterEncodingConfig{
Name: "Location",
Explode: false,
}
if err := h.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := response.Location.Get(); ok {
return e.EncodeValue(conv.StringToString(val))
}
return nil
}); err != nil {
return errors.Wrap(err, "encode Location header")
}
}
}
w.WriteHeader(302)
span.SetStatus(codes.Ok, http.StatusText(302))
return nil
}
func encodeGetMapResponse(response *Map, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
@@ -366,6 +435,20 @@ func encodeGetScriptPolicyResponse(response *ScriptPolicy, w http.ResponseWriter
return nil
}
func encodeGetStatsResponse(response *Stats, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
@@ -380,6 +463,32 @@ func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, sp
return nil
}
func encodeGetUserThumbnailResponse(response *GetUserThumbnailFound, w http.ResponseWriter, span trace.Span) error {
// Encoding response headers.
{
h := uri.NewHeaderEncoder(w.Header())
// Encode "Location" header.
{
cfg := uri.HeaderParameterEncodingConfig{
Name: "Location",
Explode: false,
}
if err := h.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := response.Location.Get(); ok {
return e.EncodeValue(conv.StringToString(val))
}
return nil
}); err != nil {
return errors.Wrap(err, "encode Location header")
}
}
}
w.WriteHeader(302)
span.SetStatus(codes.Ok, http.StatusText(302))
return nil
}
func encodeListMapfixAuditEventsResponse(response []AuditEvent, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)

View File

@@ -939,6 +939,26 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
case 't': // Prefix: "tats"
if l := len("tats"); len(elem) >= l && elem[0:l] == "tats" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "GET":
s.handleGetStatsRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET")
}
return
}
case 'u': // Prefix: "ubmissions"
if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" {
@@ -1431,6 +1451,170 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
case 't': // Prefix: "thumbnails/"
if l := len("thumbnails/"); len(elem) >= l && elem[0:l] == "thumbnails/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'a': // Prefix: "asset"
if l := len("asset"); len(elem) >= l && elem[0:l] == "asset" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "AssetID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "GET":
s.handleGetAssetThumbnailRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET")
}
return
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleBatchAssetThumbnailsRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
case 'u': // Prefix: "user"
if l := len("user"); len(elem) >= l && elem[0:l] == "user" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "UserID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "GET":
s.handleGetUserThumbnailRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "GET")
}
return
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleBatchUserThumbnailsRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
}
case 'u': // Prefix: "usernames"
if l := len("usernames"); len(elem) >= l && elem[0:l] == "usernames" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleBatchUsernamesRequest([0]string{}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
}
}
@@ -1440,12 +1624,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Route is route object.
type Route struct {
name string
summary string
operationID string
pathPattern string
count int
args [1]string
name string
summary string
operationID string
operationGroup string
pathPattern string
count int
args [1]string
}
// Name returns ogen operation name.
@@ -1465,6 +1650,11 @@ func (r Route) OperationID() string {
return r.operationID
}
// OperationGroup returns the x-ogen-operation-group value.
func (r Route) OperationGroup() string {
return r.operationGroup
}
// PathPattern returns OpenAPI path.
func (r Route) PathPattern() string {
return r.pathPattern
@@ -1551,6 +1741,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListMapfixesOperation
r.summary = "Get list of mapfixes"
r.operationID = "listMapfixes"
r.operationGroup = ""
r.pathPattern = "/mapfixes"
r.args = args
r.count = 0
@@ -1559,6 +1750,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateMapfixOperation
r.summary = "Trigger the validator to create a mapfix"
r.operationID = "createMapfix"
r.operationGroup = ""
r.pathPattern = "/mapfixes"
r.args = args
r.count = 0
@@ -1591,6 +1783,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetMapfixOperation
r.summary = "Retrieve map with ID"
r.operationID = "getMapfix"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}"
r.args = args
r.count = 1
@@ -1627,6 +1820,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListMapfixAuditEventsOperation
r.summary = "Retrieve a list of audit events"
r.operationID = "listMapfixAuditEvents"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/audit-events"
r.args = args
r.count = 1
@@ -1663,6 +1857,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateMapfixAuditCommentOperation
r.summary = "Post a comment to the audit log"
r.operationID = "createMapfixAuditComment"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/comment"
r.args = args
r.count = 1
@@ -1687,6 +1882,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = SetMapfixCompletedOperation
r.summary = "Called by maptest when a player completes the map"
r.operationID = "setMapfixCompleted"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/completed"
r.args = args
r.count = 1
@@ -1713,6 +1909,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = UpdateMapfixModelOperation
r.summary = "Update model following role restrictions"
r.operationID = "updateMapfixModel"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/model"
r.args = args
r.count = 1
@@ -1761,6 +1958,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixRejectOperation
r.summary = "Role Reviewer changes status from Submitted -> Rejected"
r.operationID = "actionMapfixReject"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/reject"
r.args = args
r.count = 1
@@ -1785,6 +1983,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixRequestChangesOperation
r.summary = "Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested"
r.operationID = "actionMapfixRequestChanges"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/request-changes"
r.args = args
r.count = 1
@@ -1821,6 +2020,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixUploadedOperation
r.summary = "Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded"
r.operationID = "actionMapfixUploaded"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-releasing"
r.args = args
r.count = 1
@@ -1845,6 +2045,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixResetSubmittingOperation
r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction"
r.operationID = "actionMapfixResetSubmitting"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-submitting"
r.args = args
r.count = 1
@@ -1869,6 +2070,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixValidatedOperation
r.summary = "Role MapfixUpload manually resets uploading softlock and changes status from Uploading -> Validated"
r.operationID = "actionMapfixValidated"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-uploading"
r.args = args
r.count = 1
@@ -1893,6 +2095,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixAcceptedOperation
r.summary = "Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted"
r.operationID = "actionMapfixAccepted"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/reset-validating"
r.args = args
r.count = 1
@@ -1919,6 +2122,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixRetryValidateOperation
r.summary = "Role Reviewer re-runs validation and changes status from Accepted -> Validating"
r.operationID = "actionMapfixRetryValidate"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/retry-validate"
r.args = args
r.count = 1
@@ -1943,6 +2147,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixRevokeOperation
r.summary = "Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction"
r.operationID = "actionMapfixRevoke"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/revoke"
r.args = args
r.count = 1
@@ -1981,6 +2186,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixTriggerReleaseOperation
r.summary = "Role MapfixUpload changes status from Uploaded -> Releasing"
r.operationID = "actionMapfixTriggerRelease"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-release"
r.args = args
r.count = 1
@@ -2004,6 +2210,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixTriggerSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting"
r.operationID = "actionMapfixTriggerSubmit"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-submit"
r.args = args
r.count = 1
@@ -2028,6 +2235,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixTriggerSubmitUncheckedOperation
r.summary = "Role Reviewer changes status from ChangesRequested -> Submitting"
r.operationID = "actionMapfixTriggerSubmitUnchecked"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-submit-unchecked"
r.args = args
r.count = 1
@@ -2054,6 +2262,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixTriggerUploadOperation
r.summary = "Role MapfixUpload changes status from Validated -> Uploading"
r.operationID = "actionMapfixTriggerUpload"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-upload"
r.args = args
r.count = 1
@@ -2078,6 +2287,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionMapfixTriggerValidateOperation
r.summary = "Role Reviewer triggers validation and changes status from Submitted -> Validating"
r.operationID = "actionMapfixTriggerValidate"
r.operationGroup = ""
r.pathPattern = "/mapfixes/{MapfixID}/status/trigger-validate"
r.args = args
r.count = 1
@@ -2111,6 +2321,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListMapsOperation
r.summary = "Get list of maps"
r.operationID = "listMaps"
r.operationGroup = ""
r.pathPattern = "/maps"
r.args = args
r.count = 0
@@ -2143,6 +2354,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetMapOperation
r.summary = "Retrieve map with ID"
r.operationID = "getMap"
r.operationGroup = ""
r.pathPattern = "/maps/{MapID}"
r.args = args
r.count = 1
@@ -2167,6 +2379,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = DownloadMapAssetOperation
r.summary = "Download the map asset"
r.operationID = "downloadMapAsset"
r.operationGroup = ""
r.pathPattern = "/maps/{MapID}/download"
r.args = args
r.count = 1
@@ -2206,6 +2419,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetOperationOperation
r.summary = "Retrieve operation with ID"
r.operationID = "getOperation"
r.operationGroup = ""
r.pathPattern = "/operations/{OperationID}"
r.args = args
r.count = 1
@@ -2230,6 +2444,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ReleaseSubmissionsOperation
r.summary = "Release a set of uploaded maps. Role SubmissionRelease"
r.operationID = "releaseSubmissions"
r.operationGroup = ""
r.pathPattern = "/release-submissions"
r.args = args
r.count = 0
@@ -2277,6 +2492,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListScriptPolicyOperation
r.summary = "Get list of script policies"
r.operationID = "listScriptPolicy"
r.operationGroup = ""
r.pathPattern = "/script-policy"
r.args = args
r.count = 0
@@ -2285,6 +2501,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateScriptPolicyOperation
r.summary = "Create a new script policy"
r.operationID = "createScriptPolicy"
r.operationGroup = ""
r.pathPattern = "/script-policy"
r.args = args
r.count = 0
@@ -2318,6 +2535,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = DeleteScriptPolicyOperation
r.summary = "Delete the specified script policy by ID"
r.operationID = "deleteScriptPolicy"
r.operationGroup = ""
r.pathPattern = "/script-policy/{ScriptPolicyID}"
r.args = args
r.count = 1
@@ -2326,6 +2544,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetScriptPolicyOperation
r.summary = "Get the specified script policy by ID"
r.operationID = "getScriptPolicy"
r.operationGroup = ""
r.pathPattern = "/script-policy/{ScriptPolicyID}"
r.args = args
r.count = 1
@@ -2334,6 +2553,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = UpdateScriptPolicyOperation
r.summary = "Update the specified script policy by ID"
r.operationID = "updateScriptPolicy"
r.operationGroup = ""
r.pathPattern = "/script-policy/{ScriptPolicyID}"
r.args = args
r.count = 1
@@ -2359,6 +2579,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListScriptsOperation
r.summary = "Get list of scripts"
r.operationID = "listScripts"
r.operationGroup = ""
r.pathPattern = "/scripts"
r.args = args
r.count = 0
@@ -2367,6 +2588,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateScriptOperation
r.summary = "Create a new script"
r.operationID = "createScript"
r.operationGroup = ""
r.pathPattern = "/scripts"
r.args = args
r.count = 0
@@ -2400,6 +2622,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = DeleteScriptOperation
r.summary = "Delete the specified script by ID"
r.operationID = "deleteScript"
r.operationGroup = ""
r.pathPattern = "/scripts/{ScriptID}"
r.args = args
r.count = 1
@@ -2408,6 +2631,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetScriptOperation
r.summary = "Get the specified script by ID"
r.operationID = "getScript"
r.operationGroup = ""
r.pathPattern = "/scripts/{ScriptID}"
r.args = args
r.count = 1
@@ -2416,6 +2640,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = UpdateScriptOperation
r.summary = "Update the specified script by ID"
r.operationID = "updateScript"
r.operationGroup = ""
r.pathPattern = "/scripts/{ScriptID}"
r.args = args
r.count = 1
@@ -2456,6 +2681,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = SessionRolesOperation
r.summary = "Get list of roles for the current session"
r.operationID = "sessionRoles"
r.operationGroup = ""
r.pathPattern = "/session/roles"
r.args = args
r.count = 0
@@ -2480,6 +2706,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = SessionUserOperation
r.summary = "Get information about the currently logged in user"
r.operationID = "sessionUser"
r.operationGroup = ""
r.pathPattern = "/session/user"
r.args = args
r.count = 0
@@ -2504,6 +2731,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = SessionValidateOperation
r.summary = "Ask if the current session is valid"
r.operationID = "sessionValidate"
r.operationGroup = ""
r.pathPattern = "/session/validate"
r.args = args
r.count = 0
@@ -2515,6 +2743,31 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
}
case 't': // Prefix: "tats"
if l := len("tats"); len(elem) >= l && elem[0:l] == "tats" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "GET":
r.name = GetStatsOperation
r.summary = "Get aggregate statistics"
r.operationID = "getStats"
r.operationGroup = ""
r.pathPattern = "/stats"
r.args = args
r.count = 0
return r, true
default:
return
}
}
case 'u': // Prefix: "ubmissions"
if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" {
@@ -2529,6 +2782,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListSubmissionsOperation
r.summary = "Get list of submissions"
r.operationID = "listSubmissions"
r.operationGroup = ""
r.pathPattern = "/submissions"
r.args = args
r.count = 0
@@ -2537,6 +2791,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateSubmissionOperation
r.summary = "Trigger the validator to create a new submission"
r.operationID = "createSubmission"
r.operationGroup = ""
r.pathPattern = "/submissions"
r.args = args
r.count = 0
@@ -2561,6 +2816,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateSubmissionAdminOperation
r.summary = "Trigger the validator to create a new submission"
r.operationID = "createSubmissionAdmin"
r.operationGroup = ""
r.pathPattern = "/submissions-admin"
r.args = args
r.count = 0
@@ -2593,6 +2849,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = GetSubmissionOperation
r.summary = "Retrieve map with ID"
r.operationID = "getSubmission"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}"
r.args = args
r.count = 1
@@ -2629,6 +2886,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ListSubmissionAuditEventsOperation
r.summary = "Retrieve a list of audit events"
r.operationID = "listSubmissionAuditEvents"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/audit-events"
r.args = args
r.count = 1
@@ -2665,6 +2923,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = CreateSubmissionAuditCommentOperation
r.summary = "Post a comment to the audit log"
r.operationID = "createSubmissionAuditComment"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/comment"
r.args = args
r.count = 1
@@ -2689,6 +2948,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = SetSubmissionCompletedOperation
r.summary = "Called by maptest when a player completes the map"
r.operationID = "setSubmissionCompleted"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/completed"
r.args = args
r.count = 1
@@ -2715,6 +2975,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = UpdateSubmissionModelOperation
r.summary = "Update model following role restrictions"
r.operationID = "updateSubmissionModel"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/model"
r.args = args
r.count = 1
@@ -2763,6 +3024,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionRejectOperation
r.summary = "Role Reviewer changes status from Submitted -> Rejected"
r.operationID = "actionSubmissionReject"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/reject"
r.args = args
r.count = 1
@@ -2787,6 +3049,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionRequestChangesOperation
r.summary = "Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested"
r.operationID = "actionSubmissionRequestChanges"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/request-changes"
r.args = args
r.count = 1
@@ -2823,6 +3086,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionResetSubmittingOperation
r.summary = "Role Submitter manually resets submitting softlock and changes status from Submitting -> UnderConstruction"
r.operationID = "actionSubmissionResetSubmitting"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/reset-submitting"
r.args = args
r.count = 1
@@ -2847,6 +3111,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionValidatedOperation
r.summary = "Role SubmissionUpload manually resets uploading softlock and changes status from Uploading -> Validated"
r.operationID = "actionSubmissionValidated"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/reset-uploading"
r.args = args
r.count = 1
@@ -2871,6 +3136,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionAcceptedOperation
r.summary = "Role Reviewer manually resets validating softlock and changes status from Validating -> Accepted"
r.operationID = "actionSubmissionAccepted"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/reset-validating"
r.args = args
r.count = 1
@@ -2897,6 +3163,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionRetryValidateOperation
r.summary = "Role Reviewer re-runs validation and changes status from Accepted -> Validating"
r.operationID = "actionSubmissionRetryValidate"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/retry-validate"
r.args = args
r.count = 1
@@ -2921,6 +3188,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionRevokeOperation
r.summary = "Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction"
r.operationID = "actionSubmissionRevoke"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/revoke"
r.args = args
r.count = 1
@@ -2958,6 +3226,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionTriggerSubmitOperation
r.summary = "Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting"
r.operationID = "actionSubmissionTriggerSubmit"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-submit"
r.args = args
r.count = 1
@@ -2982,6 +3251,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionTriggerSubmitUncheckedOperation
r.summary = "Role Reviewer changes status from ChangesRequested -> Submitting"
r.operationID = "actionSubmissionTriggerSubmitUnchecked"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-submit-unchecked"
r.args = args
r.count = 1
@@ -3008,6 +3278,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionTriggerUploadOperation
r.summary = "Role SubmissionUpload changes status from Validated -> Uploading"
r.operationID = "actionSubmissionTriggerUpload"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-upload"
r.args = args
r.count = 1
@@ -3032,6 +3303,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionSubmissionTriggerValidateOperation
r.summary = "Role Reviewer triggers validation and changes status from Submitted -> Validating"
r.operationID = "actionSubmissionTriggerValidate"
r.operationGroup = ""
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-validate"
r.args = args
r.count = 1
@@ -3053,6 +3325,191 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
}
case 't': // Prefix: "thumbnails/"
if l := len("thumbnails/"); len(elem) >= l && elem[0:l] == "thumbnails/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'a': // Prefix: "asset"
if l := len("asset"); len(elem) >= l && elem[0:l] == "asset" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "AssetID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch method {
case "GET":
r.name = GetAssetThumbnailOperation
r.summary = "Get single asset thumbnail"
r.operationID = "getAssetThumbnail"
r.operationGroup = ""
r.pathPattern = "/thumbnails/asset/{AssetID}"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = BatchAssetThumbnailsOperation
r.summary = "Batch fetch asset thumbnails"
r.operationID = "batchAssetThumbnails"
r.operationGroup = ""
r.pathPattern = "/thumbnails/assets"
r.args = args
r.count = 0
return r, true
default:
return
}
}
}
case 'u': // Prefix: "user"
if l := len("user"); len(elem) >= l && elem[0:l] == "user" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// Param: "UserID"
// Leaf parameter, slashes are prohibited
idx := strings.IndexByte(elem, '/')
if idx >= 0 {
break
}
args[0] = elem
elem = ""
if len(elem) == 0 {
// Leaf node.
switch method {
case "GET":
r.name = GetUserThumbnailOperation
r.summary = "Get single user avatar thumbnail"
r.operationID = "getUserThumbnail"
r.operationGroup = ""
r.pathPattern = "/thumbnails/user/{UserID}"
r.args = args
r.count = 1
return r, true
default:
return
}
}
case 's': // Prefix: "s"
if l := len("s"); len(elem) >= l && elem[0:l] == "s" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = BatchUserThumbnailsOperation
r.summary = "Batch fetch user avatar thumbnails"
r.operationID = "batchUserThumbnails"
r.operationGroup = ""
r.pathPattern = "/thumbnails/users"
r.args = args
r.count = 0
return r, true
default:
return
}
}
}
}
case 'u': // Prefix: "usernames"
if l := len("usernames"); len(elem) >= l && elem[0:l] == "usernames" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = BatchUsernamesOperation
r.summary = "Batch fetch usernames"
r.operationID = "batchUsernames"
r.operationGroup = ""
r.pathPattern = "/usernames"
r.args = args
r.count = 0
return r, true
default:
return
}
}
}
}

View File

@@ -7,6 +7,7 @@ import (
"io"
"time"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
)
@@ -192,6 +193,254 @@ func (s *AuditEventEventData) init() AuditEventEventData {
return m
}
type BatchAssetThumbnailsOK struct {
// Map of asset ID to thumbnail URL.
Thumbnails OptBatchAssetThumbnailsOKThumbnails `json:"thumbnails"`
}
// GetThumbnails returns the value of Thumbnails.
func (s *BatchAssetThumbnailsOK) GetThumbnails() OptBatchAssetThumbnailsOKThumbnails {
return s.Thumbnails
}
// SetThumbnails sets the value of Thumbnails.
func (s *BatchAssetThumbnailsOK) SetThumbnails(val OptBatchAssetThumbnailsOKThumbnails) {
s.Thumbnails = val
}
// Map of asset ID to thumbnail URL.
type BatchAssetThumbnailsOKThumbnails map[string]string
func (s *BatchAssetThumbnailsOKThumbnails) init() BatchAssetThumbnailsOKThumbnails {
m := *s
if m == nil {
m = map[string]string{}
*s = m
}
return m
}
type BatchAssetThumbnailsReq struct {
// Array of asset IDs (max 100).
AssetIds []uint64 `json:"assetIds"`
// Thumbnail size.
Size OptBatchAssetThumbnailsReqSize `json:"size"`
}
// GetAssetIds returns the value of AssetIds.
func (s *BatchAssetThumbnailsReq) GetAssetIds() []uint64 {
return s.AssetIds
}
// GetSize returns the value of Size.
func (s *BatchAssetThumbnailsReq) GetSize() OptBatchAssetThumbnailsReqSize {
return s.Size
}
// SetAssetIds sets the value of AssetIds.
func (s *BatchAssetThumbnailsReq) SetAssetIds(val []uint64) {
s.AssetIds = val
}
// SetSize sets the value of Size.
func (s *BatchAssetThumbnailsReq) SetSize(val OptBatchAssetThumbnailsReqSize) {
s.Size = val
}
// Thumbnail size.
type BatchAssetThumbnailsReqSize string
const (
BatchAssetThumbnailsReqSize150x150 BatchAssetThumbnailsReqSize = "150x150"
BatchAssetThumbnailsReqSize420x420 BatchAssetThumbnailsReqSize = "420x420"
BatchAssetThumbnailsReqSize768x432 BatchAssetThumbnailsReqSize = "768x432"
)
// AllValues returns all BatchAssetThumbnailsReqSize values.
func (BatchAssetThumbnailsReqSize) AllValues() []BatchAssetThumbnailsReqSize {
return []BatchAssetThumbnailsReqSize{
BatchAssetThumbnailsReqSize150x150,
BatchAssetThumbnailsReqSize420x420,
BatchAssetThumbnailsReqSize768x432,
}
}
// MarshalText implements encoding.TextMarshaler.
func (s BatchAssetThumbnailsReqSize) MarshalText() ([]byte, error) {
switch s {
case BatchAssetThumbnailsReqSize150x150:
return []byte(s), nil
case BatchAssetThumbnailsReqSize420x420:
return []byte(s), nil
case BatchAssetThumbnailsReqSize768x432:
return []byte(s), nil
default:
return nil, errors.Errorf("invalid value: %q", s)
}
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (s *BatchAssetThumbnailsReqSize) UnmarshalText(data []byte) error {
switch BatchAssetThumbnailsReqSize(data) {
case BatchAssetThumbnailsReqSize150x150:
*s = BatchAssetThumbnailsReqSize150x150
return nil
case BatchAssetThumbnailsReqSize420x420:
*s = BatchAssetThumbnailsReqSize420x420
return nil
case BatchAssetThumbnailsReqSize768x432:
*s = BatchAssetThumbnailsReqSize768x432
return nil
default:
return errors.Errorf("invalid value: %q", data)
}
}
type BatchUserThumbnailsOK struct {
// Map of user ID to thumbnail URL.
Thumbnails OptBatchUserThumbnailsOKThumbnails `json:"thumbnails"`
}
// GetThumbnails returns the value of Thumbnails.
func (s *BatchUserThumbnailsOK) GetThumbnails() OptBatchUserThumbnailsOKThumbnails {
return s.Thumbnails
}
// SetThumbnails sets the value of Thumbnails.
func (s *BatchUserThumbnailsOK) SetThumbnails(val OptBatchUserThumbnailsOKThumbnails) {
s.Thumbnails = val
}
// Map of user ID to thumbnail URL.
type BatchUserThumbnailsOKThumbnails map[string]string
func (s *BatchUserThumbnailsOKThumbnails) init() BatchUserThumbnailsOKThumbnails {
m := *s
if m == nil {
m = map[string]string{}
*s = m
}
return m
}
type BatchUserThumbnailsReq struct {
// Array of user IDs (max 100).
UserIds []uint64 `json:"userIds"`
// Thumbnail size.
Size OptBatchUserThumbnailsReqSize `json:"size"`
}
// GetUserIds returns the value of UserIds.
func (s *BatchUserThumbnailsReq) GetUserIds() []uint64 {
return s.UserIds
}
// GetSize returns the value of Size.
func (s *BatchUserThumbnailsReq) GetSize() OptBatchUserThumbnailsReqSize {
return s.Size
}
// SetUserIds sets the value of UserIds.
func (s *BatchUserThumbnailsReq) SetUserIds(val []uint64) {
s.UserIds = val
}
// SetSize sets the value of Size.
func (s *BatchUserThumbnailsReq) SetSize(val OptBatchUserThumbnailsReqSize) {
s.Size = val
}
// Thumbnail size.
type BatchUserThumbnailsReqSize string
const (
BatchUserThumbnailsReqSize150x150 BatchUserThumbnailsReqSize = "150x150"
BatchUserThumbnailsReqSize420x420 BatchUserThumbnailsReqSize = "420x420"
BatchUserThumbnailsReqSize768x432 BatchUserThumbnailsReqSize = "768x432"
)
// AllValues returns all BatchUserThumbnailsReqSize values.
func (BatchUserThumbnailsReqSize) AllValues() []BatchUserThumbnailsReqSize {
return []BatchUserThumbnailsReqSize{
BatchUserThumbnailsReqSize150x150,
BatchUserThumbnailsReqSize420x420,
BatchUserThumbnailsReqSize768x432,
}
}
// MarshalText implements encoding.TextMarshaler.
func (s BatchUserThumbnailsReqSize) MarshalText() ([]byte, error) {
switch s {
case BatchUserThumbnailsReqSize150x150:
return []byte(s), nil
case BatchUserThumbnailsReqSize420x420:
return []byte(s), nil
case BatchUserThumbnailsReqSize768x432:
return []byte(s), nil
default:
return nil, errors.Errorf("invalid value: %q", s)
}
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (s *BatchUserThumbnailsReqSize) UnmarshalText(data []byte) error {
switch BatchUserThumbnailsReqSize(data) {
case BatchUserThumbnailsReqSize150x150:
*s = BatchUserThumbnailsReqSize150x150
return nil
case BatchUserThumbnailsReqSize420x420:
*s = BatchUserThumbnailsReqSize420x420
return nil
case BatchUserThumbnailsReqSize768x432:
*s = BatchUserThumbnailsReqSize768x432
return nil
default:
return errors.Errorf("invalid value: %q", data)
}
}
type BatchUsernamesOK struct {
// Map of user ID to username.
Usernames OptBatchUsernamesOKUsernames `json:"usernames"`
}
// GetUsernames returns the value of Usernames.
func (s *BatchUsernamesOK) GetUsernames() OptBatchUsernamesOKUsernames {
return s.Usernames
}
// SetUsernames sets the value of Usernames.
func (s *BatchUsernamesOK) SetUsernames(val OptBatchUsernamesOKUsernames) {
s.Usernames = val
}
// Map of user ID to username.
type BatchUsernamesOKUsernames map[string]string
func (s *BatchUsernamesOKUsernames) init() BatchUsernamesOKUsernames {
m := *s
if m == nil {
m = map[string]string{}
*s = m
}
return m
}
type BatchUsernamesReq struct {
// Array of user IDs (max 100).
UserIds []uint64 `json:"userIds"`
}
// GetUserIds returns the value of UserIds.
func (s *BatchUsernamesReq) GetUserIds() []uint64 {
return s.UserIds
}
// SetUserIds sets the value of UserIds.
func (s *BatchUsernamesReq) SetUserIds(val []uint64) {
s.UserIds = val
}
type CookieAuth struct {
APIKey string
Roles []string
@@ -324,6 +573,132 @@ func (s *ErrorStatusCode) SetResponse(val Error) {
s.Response = val
}
// GetAssetThumbnailFound is response for GetAssetThumbnail operation.
type GetAssetThumbnailFound struct {
Location OptString
}
// GetLocation returns the value of Location.
func (s *GetAssetThumbnailFound) GetLocation() OptString {
return s.Location
}
// SetLocation sets the value of Location.
func (s *GetAssetThumbnailFound) SetLocation(val OptString) {
s.Location = val
}
type GetAssetThumbnailSize string
const (
GetAssetThumbnailSize150x150 GetAssetThumbnailSize = "150x150"
GetAssetThumbnailSize420x420 GetAssetThumbnailSize = "420x420"
GetAssetThumbnailSize768x432 GetAssetThumbnailSize = "768x432"
)
// AllValues returns all GetAssetThumbnailSize values.
func (GetAssetThumbnailSize) AllValues() []GetAssetThumbnailSize {
return []GetAssetThumbnailSize{
GetAssetThumbnailSize150x150,
GetAssetThumbnailSize420x420,
GetAssetThumbnailSize768x432,
}
}
// MarshalText implements encoding.TextMarshaler.
func (s GetAssetThumbnailSize) MarshalText() ([]byte, error) {
switch s {
case GetAssetThumbnailSize150x150:
return []byte(s), nil
case GetAssetThumbnailSize420x420:
return []byte(s), nil
case GetAssetThumbnailSize768x432:
return []byte(s), nil
default:
return nil, errors.Errorf("invalid value: %q", s)
}
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (s *GetAssetThumbnailSize) UnmarshalText(data []byte) error {
switch GetAssetThumbnailSize(data) {
case GetAssetThumbnailSize150x150:
*s = GetAssetThumbnailSize150x150
return nil
case GetAssetThumbnailSize420x420:
*s = GetAssetThumbnailSize420x420
return nil
case GetAssetThumbnailSize768x432:
*s = GetAssetThumbnailSize768x432
return nil
default:
return errors.Errorf("invalid value: %q", data)
}
}
// GetUserThumbnailFound is response for GetUserThumbnail operation.
type GetUserThumbnailFound struct {
Location OptString
}
// GetLocation returns the value of Location.
func (s *GetUserThumbnailFound) GetLocation() OptString {
return s.Location
}
// SetLocation sets the value of Location.
func (s *GetUserThumbnailFound) SetLocation(val OptString) {
s.Location = val
}
type GetUserThumbnailSize string
const (
GetUserThumbnailSize150x150 GetUserThumbnailSize = "150x150"
GetUserThumbnailSize420x420 GetUserThumbnailSize = "420x420"
GetUserThumbnailSize768x432 GetUserThumbnailSize = "768x432"
)
// AllValues returns all GetUserThumbnailSize values.
func (GetUserThumbnailSize) AllValues() []GetUserThumbnailSize {
return []GetUserThumbnailSize{
GetUserThumbnailSize150x150,
GetUserThumbnailSize420x420,
GetUserThumbnailSize768x432,
}
}
// MarshalText implements encoding.TextMarshaler.
func (s GetUserThumbnailSize) MarshalText() ([]byte, error) {
switch s {
case GetUserThumbnailSize150x150:
return []byte(s), nil
case GetUserThumbnailSize420x420:
return []byte(s), nil
case GetUserThumbnailSize768x432:
return []byte(s), nil
default:
return nil, errors.Errorf("invalid value: %q", s)
}
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (s *GetUserThumbnailSize) UnmarshalText(data []byte) error {
switch GetUserThumbnailSize(data) {
case GetUserThumbnailSize150x150:
*s = GetUserThumbnailSize150x150
return nil
case GetUserThumbnailSize420x420:
*s = GetUserThumbnailSize420x420
return nil
case GetUserThumbnailSize768x432:
*s = GetUserThumbnailSize768x432
return nil
default:
return errors.Errorf("invalid value: %q", data)
}
}
// Ref: #/components/schemas/Map
type Map struct {
ID int64 `json:"ID"`
@@ -777,6 +1152,328 @@ func (s *OperationID) SetOperationID(val int32) {
s.OperationID = val
}
// NewOptBatchAssetThumbnailsOKThumbnails returns new OptBatchAssetThumbnailsOKThumbnails with value set to v.
func NewOptBatchAssetThumbnailsOKThumbnails(v BatchAssetThumbnailsOKThumbnails) OptBatchAssetThumbnailsOKThumbnails {
return OptBatchAssetThumbnailsOKThumbnails{
Value: v,
Set: true,
}
}
// OptBatchAssetThumbnailsOKThumbnails is optional BatchAssetThumbnailsOKThumbnails.
type OptBatchAssetThumbnailsOKThumbnails struct {
Value BatchAssetThumbnailsOKThumbnails
Set bool
}
// IsSet returns true if OptBatchAssetThumbnailsOKThumbnails was set.
func (o OptBatchAssetThumbnailsOKThumbnails) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptBatchAssetThumbnailsOKThumbnails) Reset() {
var v BatchAssetThumbnailsOKThumbnails
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptBatchAssetThumbnailsOKThumbnails) SetTo(v BatchAssetThumbnailsOKThumbnails) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptBatchAssetThumbnailsOKThumbnails) Get() (v BatchAssetThumbnailsOKThumbnails, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptBatchAssetThumbnailsOKThumbnails) Or(d BatchAssetThumbnailsOKThumbnails) BatchAssetThumbnailsOKThumbnails {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptBatchAssetThumbnailsReqSize returns new OptBatchAssetThumbnailsReqSize with value set to v.
func NewOptBatchAssetThumbnailsReqSize(v BatchAssetThumbnailsReqSize) OptBatchAssetThumbnailsReqSize {
return OptBatchAssetThumbnailsReqSize{
Value: v,
Set: true,
}
}
// OptBatchAssetThumbnailsReqSize is optional BatchAssetThumbnailsReqSize.
type OptBatchAssetThumbnailsReqSize struct {
Value BatchAssetThumbnailsReqSize
Set bool
}
// IsSet returns true if OptBatchAssetThumbnailsReqSize was set.
func (o OptBatchAssetThumbnailsReqSize) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptBatchAssetThumbnailsReqSize) Reset() {
var v BatchAssetThumbnailsReqSize
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptBatchAssetThumbnailsReqSize) SetTo(v BatchAssetThumbnailsReqSize) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptBatchAssetThumbnailsReqSize) Get() (v BatchAssetThumbnailsReqSize, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptBatchAssetThumbnailsReqSize) Or(d BatchAssetThumbnailsReqSize) BatchAssetThumbnailsReqSize {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptBatchUserThumbnailsOKThumbnails returns new OptBatchUserThumbnailsOKThumbnails with value set to v.
func NewOptBatchUserThumbnailsOKThumbnails(v BatchUserThumbnailsOKThumbnails) OptBatchUserThumbnailsOKThumbnails {
return OptBatchUserThumbnailsOKThumbnails{
Value: v,
Set: true,
}
}
// OptBatchUserThumbnailsOKThumbnails is optional BatchUserThumbnailsOKThumbnails.
type OptBatchUserThumbnailsOKThumbnails struct {
Value BatchUserThumbnailsOKThumbnails
Set bool
}
// IsSet returns true if OptBatchUserThumbnailsOKThumbnails was set.
func (o OptBatchUserThumbnailsOKThumbnails) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptBatchUserThumbnailsOKThumbnails) Reset() {
var v BatchUserThumbnailsOKThumbnails
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptBatchUserThumbnailsOKThumbnails) SetTo(v BatchUserThumbnailsOKThumbnails) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptBatchUserThumbnailsOKThumbnails) Get() (v BatchUserThumbnailsOKThumbnails, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptBatchUserThumbnailsOKThumbnails) Or(d BatchUserThumbnailsOKThumbnails) BatchUserThumbnailsOKThumbnails {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptBatchUserThumbnailsReqSize returns new OptBatchUserThumbnailsReqSize with value set to v.
func NewOptBatchUserThumbnailsReqSize(v BatchUserThumbnailsReqSize) OptBatchUserThumbnailsReqSize {
return OptBatchUserThumbnailsReqSize{
Value: v,
Set: true,
}
}
// OptBatchUserThumbnailsReqSize is optional BatchUserThumbnailsReqSize.
type OptBatchUserThumbnailsReqSize struct {
Value BatchUserThumbnailsReqSize
Set bool
}
// IsSet returns true if OptBatchUserThumbnailsReqSize was set.
func (o OptBatchUserThumbnailsReqSize) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptBatchUserThumbnailsReqSize) Reset() {
var v BatchUserThumbnailsReqSize
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptBatchUserThumbnailsReqSize) SetTo(v BatchUserThumbnailsReqSize) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptBatchUserThumbnailsReqSize) Get() (v BatchUserThumbnailsReqSize, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptBatchUserThumbnailsReqSize) Or(d BatchUserThumbnailsReqSize) BatchUserThumbnailsReqSize {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptBatchUsernamesOKUsernames returns new OptBatchUsernamesOKUsernames with value set to v.
func NewOptBatchUsernamesOKUsernames(v BatchUsernamesOKUsernames) OptBatchUsernamesOKUsernames {
return OptBatchUsernamesOKUsernames{
Value: v,
Set: true,
}
}
// OptBatchUsernamesOKUsernames is optional BatchUsernamesOKUsernames.
type OptBatchUsernamesOKUsernames struct {
Value BatchUsernamesOKUsernames
Set bool
}
// IsSet returns true if OptBatchUsernamesOKUsernames was set.
func (o OptBatchUsernamesOKUsernames) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptBatchUsernamesOKUsernames) Reset() {
var v BatchUsernamesOKUsernames
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptBatchUsernamesOKUsernames) SetTo(v BatchUsernamesOKUsernames) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptBatchUsernamesOKUsernames) Get() (v BatchUsernamesOKUsernames, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptBatchUsernamesOKUsernames) Or(d BatchUsernamesOKUsernames) BatchUsernamesOKUsernames {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptGetAssetThumbnailSize returns new OptGetAssetThumbnailSize with value set to v.
func NewOptGetAssetThumbnailSize(v GetAssetThumbnailSize) OptGetAssetThumbnailSize {
return OptGetAssetThumbnailSize{
Value: v,
Set: true,
}
}
// OptGetAssetThumbnailSize is optional GetAssetThumbnailSize.
type OptGetAssetThumbnailSize struct {
Value GetAssetThumbnailSize
Set bool
}
// IsSet returns true if OptGetAssetThumbnailSize was set.
func (o OptGetAssetThumbnailSize) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptGetAssetThumbnailSize) Reset() {
var v GetAssetThumbnailSize
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptGetAssetThumbnailSize) SetTo(v GetAssetThumbnailSize) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptGetAssetThumbnailSize) Get() (v GetAssetThumbnailSize, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptGetAssetThumbnailSize) Or(d GetAssetThumbnailSize) GetAssetThumbnailSize {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptGetUserThumbnailSize returns new OptGetUserThumbnailSize with value set to v.
func NewOptGetUserThumbnailSize(v GetUserThumbnailSize) OptGetUserThumbnailSize {
return OptGetUserThumbnailSize{
Value: v,
Set: true,
}
}
// OptGetUserThumbnailSize is optional GetUserThumbnailSize.
type OptGetUserThumbnailSize struct {
Value GetUserThumbnailSize
Set bool
}
// IsSet returns true if OptGetUserThumbnailSize was set.
func (o OptGetUserThumbnailSize) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptGetUserThumbnailSize) Reset() {
var v GetUserThumbnailSize
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptGetUserThumbnailSize) SetTo(v GetUserThumbnailSize) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptGetUserThumbnailSize) Get() (v GetUserThumbnailSize, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptGetUserThumbnailSize) Or(d GetUserThumbnailSize) GetUserThumbnailSize {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptInt32 returns new OptInt32 with value set to v.
func NewOptInt32(v int32) OptInt32 {
return OptInt32{
@@ -1302,6 +1999,83 @@ type SetMapfixCompletedNoContent struct{}
// SetSubmissionCompletedNoContent is response for SetSubmissionCompleted operation.
type SetSubmissionCompletedNoContent struct{}
// Aggregate statistics for submissions and mapfixes.
// Ref: #/components/schemas/Stats
type Stats struct {
// Total number of submissions.
TotalSubmissions int64 `json:"TotalSubmissions"`
// Total number of mapfixes.
TotalMapfixes int64 `json:"TotalMapfixes"`
// Number of released submissions.
ReleasedSubmissions int64 `json:"ReleasedSubmissions"`
// Number of released mapfixes.
ReleasedMapfixes int64 `json:"ReleasedMapfixes"`
// Number of submissions under review.
SubmittedSubmissions int64 `json:"SubmittedSubmissions"`
// Number of mapfixes under review.
SubmittedMapfixes int64 `json:"SubmittedMapfixes"`
}
// GetTotalSubmissions returns the value of TotalSubmissions.
func (s *Stats) GetTotalSubmissions() int64 {
return s.TotalSubmissions
}
// GetTotalMapfixes returns the value of TotalMapfixes.
func (s *Stats) GetTotalMapfixes() int64 {
return s.TotalMapfixes
}
// GetReleasedSubmissions returns the value of ReleasedSubmissions.
func (s *Stats) GetReleasedSubmissions() int64 {
return s.ReleasedSubmissions
}
// GetReleasedMapfixes returns the value of ReleasedMapfixes.
func (s *Stats) GetReleasedMapfixes() int64 {
return s.ReleasedMapfixes
}
// GetSubmittedSubmissions returns the value of SubmittedSubmissions.
func (s *Stats) GetSubmittedSubmissions() int64 {
return s.SubmittedSubmissions
}
// GetSubmittedMapfixes returns the value of SubmittedMapfixes.
func (s *Stats) GetSubmittedMapfixes() int64 {
return s.SubmittedMapfixes
}
// SetTotalSubmissions sets the value of TotalSubmissions.
func (s *Stats) SetTotalSubmissions(val int64) {
s.TotalSubmissions = val
}
// SetTotalMapfixes sets the value of TotalMapfixes.
func (s *Stats) SetTotalMapfixes(val int64) {
s.TotalMapfixes = val
}
// SetReleasedSubmissions sets the value of ReleasedSubmissions.
func (s *Stats) SetReleasedSubmissions(val int64) {
s.ReleasedSubmissions = val
}
// SetReleasedMapfixes sets the value of ReleasedMapfixes.
func (s *Stats) SetReleasedMapfixes(val int64) {
s.ReleasedMapfixes = val
}
// SetSubmittedSubmissions sets the value of SubmittedSubmissions.
func (s *Stats) SetSubmittedSubmissions(val int64) {
s.SubmittedSubmissions = val
}
// SetSubmittedMapfixes sets the value of SubmittedMapfixes.
func (s *Stats) SetSubmittedMapfixes(val int64) {
s.SubmittedMapfixes = val
}
// Ref: #/components/schemas/Submission
type Submission struct {
ID int64 `json:"ID"`

View File

@@ -8,7 +8,6 @@ import (
"strings"
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/ogenerrors"
)

View File

@@ -155,6 +155,24 @@ type Handler interface {
//
// POST /submissions/{SubmissionID}/status/reset-uploading
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
// BatchAssetThumbnails implements batchAssetThumbnails operation.
//
// Batch fetch asset thumbnails.
//
// POST /thumbnails/assets
BatchAssetThumbnails(ctx context.Context, req *BatchAssetThumbnailsReq) (*BatchAssetThumbnailsOK, error)
// BatchUserThumbnails implements batchUserThumbnails operation.
//
// Batch fetch user avatar thumbnails.
//
// POST /thumbnails/users
BatchUserThumbnails(ctx context.Context, req *BatchUserThumbnailsReq) (*BatchUserThumbnailsOK, error)
// BatchUsernames implements batchUsernames operation.
//
// Batch fetch usernames.
//
// POST /usernames
BatchUsernames(ctx context.Context, req *BatchUsernamesReq) (*BatchUsernamesOK, error)
// CreateMapfix implements createMapfix operation.
//
// Trigger the validator to create a mapfix.
@@ -215,6 +233,12 @@ type Handler interface {
//
// GET /maps/{MapID}/download
DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (DownloadMapAssetOK, error)
// GetAssetThumbnail implements getAssetThumbnail operation.
//
// Get single asset thumbnail.
//
// GET /thumbnails/asset/{AssetID}
GetAssetThumbnail(ctx context.Context, params GetAssetThumbnailParams) (*GetAssetThumbnailFound, error)
// GetMap implements getMap operation.
//
// Retrieve map with ID.
@@ -245,12 +269,24 @@ type Handler interface {
//
// GET /script-policy/{ScriptPolicyID}
GetScriptPolicy(ctx context.Context, params GetScriptPolicyParams) (*ScriptPolicy, error)
// GetStats implements getStats operation.
//
// Get aggregate statistics.
//
// GET /stats
GetStats(ctx context.Context) (*Stats, error)
// GetSubmission implements getSubmission operation.
//
// Retrieve map with ID.
//
// GET /submissions/{SubmissionID}
GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error)
// GetUserThumbnail implements getUserThumbnail operation.
//
// Get single user avatar thumbnail.
//
// GET /thumbnails/user/{UserID}
GetUserThumbnail(ctx context.Context, params GetUserThumbnailParams) (*GetUserThumbnailFound, error)
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
//
// Retrieve a list of audit events.

View File

@@ -232,6 +232,33 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param
return ht.ErrNotImplemented
}
// BatchAssetThumbnails implements batchAssetThumbnails operation.
//
// Batch fetch asset thumbnails.
//
// POST /thumbnails/assets
func (UnimplementedHandler) BatchAssetThumbnails(ctx context.Context, req *BatchAssetThumbnailsReq) (r *BatchAssetThumbnailsOK, _ error) {
return r, ht.ErrNotImplemented
}
// BatchUserThumbnails implements batchUserThumbnails operation.
//
// Batch fetch user avatar thumbnails.
//
// POST /thumbnails/users
func (UnimplementedHandler) BatchUserThumbnails(ctx context.Context, req *BatchUserThumbnailsReq) (r *BatchUserThumbnailsOK, _ error) {
return r, ht.ErrNotImplemented
}
// BatchUsernames implements batchUsernames operation.
//
// Batch fetch usernames.
//
// POST /usernames
func (UnimplementedHandler) BatchUsernames(ctx context.Context, req *BatchUsernamesReq) (r *BatchUsernamesOK, _ error) {
return r, ht.ErrNotImplemented
}
// CreateMapfix implements createMapfix operation.
//
// Trigger the validator to create a mapfix.
@@ -322,6 +349,15 @@ func (UnimplementedHandler) DownloadMapAsset(ctx context.Context, params Downloa
return r, ht.ErrNotImplemented
}
// GetAssetThumbnail implements getAssetThumbnail operation.
//
// Get single asset thumbnail.
//
// GET /thumbnails/asset/{AssetID}
func (UnimplementedHandler) GetAssetThumbnail(ctx context.Context, params GetAssetThumbnailParams) (r *GetAssetThumbnailFound, _ error) {
return r, ht.ErrNotImplemented
}
// GetMap implements getMap operation.
//
// Retrieve map with ID.
@@ -367,6 +403,15 @@ func (UnimplementedHandler) GetScriptPolicy(ctx context.Context, params GetScrip
return r, ht.ErrNotImplemented
}
// GetStats implements getStats operation.
//
// Get aggregate statistics.
//
// GET /stats
func (UnimplementedHandler) GetStats(ctx context.Context) (r *Stats, _ error) {
return r, ht.ErrNotImplemented
}
// GetSubmission implements getSubmission operation.
//
// Retrieve map with ID.
@@ -376,6 +421,15 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss
return r, ht.ErrNotImplemented
}
// GetUserThumbnail implements getUserThumbnail operation.
//
// Get single user avatar thumbnail.
//
// GET /thumbnails/user/{UserID}
func (UnimplementedHandler) GetUserThumbnail(ctx context.Context, params GetUserThumbnailParams) (r *GetUserThumbnailFound, _ error) {
return r, ht.ErrNotImplemented
}
// ListMapfixAuditEvents implements listMapfixAuditEvents operation.
//
// Retrieve a list of audit events.

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/validator_controller"
"git.itzana.me/strafesnet/maps-service/pkg/web_api"
"github.com/nats-io/nats.go"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
@@ -102,6 +103,24 @@ func NewServeCommand() *cli.Command {
EnvVars: []string{"RBX_API_KEY"},
Required: true,
},
&cli.StringFlag{
Name: "redis-host",
Usage: "Host of Redis cache",
EnvVars: []string{"REDIS_HOST"},
Value: "localhost:6379",
},
&cli.StringFlag{
Name: "redis-password",
Usage: "Password for Redis",
EnvVars: []string{"REDIS_PASSWORD"},
Value: "",
},
&cli.IntFlag{
Name: "redis-db",
Usage: "Redis database number",
EnvVars: []string{"REDIS_DB"},
Value: 0,
},
},
}
}
@@ -129,6 +148,24 @@ func serve(ctx *cli.Context) error {
log.WithError(err).Fatal("failed to add stream")
}
// Initialize Redis client
redisClient := redis.NewClient(&redis.Options{
Addr: ctx.String("redis-host"),
Password: ctx.String("redis-password"),
DB: ctx.Int("redis-db"),
})
// Test Redis connection
if err := redisClient.Ping(ctx.Context).Err(); err != nil {
log.WithError(err).Warn("failed to connect to Redis - thumbnails will not be cached")
}
// Initialize Roblox client
robloxClient := &roblox.Client{
HttpClient: http.DefaultClient,
ApiKey: ctx.String("rbx-api-key"),
}
// connect to main game database
conn, err := grpc.Dial(ctx.String("data-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
@@ -139,13 +176,15 @@ func serve(ctx *cli.Context) error {
js,
maps.NewMapsServiceClient(conn),
users.NewUsersServiceClient(conn),
robloxClient,
redisClient,
)
svc_external := web_api.NewService(
&svc_inner,
roblox.Client{
HttpClient: http.DefaultClient,
ApiKey: ctx.String("rbx-api-key"),
ApiKey: ctx.String("rbx-api-key"),
},
)

160
pkg/roblox/thumbnails.go Normal file
View File

@@ -0,0 +1,160 @@
package roblox
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// ThumbnailSize represents valid Roblox thumbnail sizes
type ThumbnailSize string
const (
Size150x150 ThumbnailSize = "150x150"
Size420x420 ThumbnailSize = "420x420"
Size768x432 ThumbnailSize = "768x432"
)
// ThumbnailFormat represents the image format
type ThumbnailFormat string
const (
FormatPng ThumbnailFormat = "Png"
FormatJpeg ThumbnailFormat = "Jpeg"
)
// ThumbnailRequest represents a single thumbnail request
type ThumbnailRequest struct {
RequestID string `json:"requestId,omitempty"`
Type string `json:"type"`
TargetID uint64 `json:"targetId"`
Size string `json:"size,omitempty"`
Format string `json:"format,omitempty"`
}
// ThumbnailData represents a single thumbnail response
type ThumbnailData struct {
TargetID uint64 `json:"targetId"`
State string `json:"state"` // "Completed", "Error", "Pending"
ImageURL string `json:"imageUrl"`
}
// BatchThumbnailsResponse represents the API response
type BatchThumbnailsResponse struct {
Data []ThumbnailData `json:"data"`
}
// GetAssetThumbnails fetches thumbnails for multiple assets in a single batch request
// Roblox allows up to 100 assets per batch
func (c *Client) GetAssetThumbnails(assetIDs []uint64, size ThumbnailSize, format ThumbnailFormat) ([]ThumbnailData, error) {
if len(assetIDs) == 0 {
return []ThumbnailData{}, nil
}
if len(assetIDs) > 100 {
return nil, GetError("batch size cannot exceed 100 assets")
}
// Build request payload - the API expects an array directly, not wrapped in an object
requests := make([]ThumbnailRequest, len(assetIDs))
for i, assetID := range assetIDs {
requests[i] = ThumbnailRequest{
Type: "Asset",
TargetID: assetID,
Size: string(size),
Format: string(format),
}
}
jsonData, err := json.Marshal(requests)
if err != nil {
return nil, GetError("JSONMarshalError: " + err.Error())
}
req, err := http.NewRequest("POST", "https://thumbnails.roblox.com/v1/batch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, GetError("RequestCreationError: " + err.Error())
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, GetError("RequestError: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, GetError(fmt.Sprintf("ResponseError: status code %d, body: %s", resp.StatusCode, string(body)))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, GetError("ReadBodyError: " + err.Error())
}
var response BatchThumbnailsResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, GetError("JSONUnmarshalError: " + err.Error())
}
return response.Data, nil
}
// GetUserAvatarThumbnails fetches avatar thumbnails for multiple users in a single batch request
func (c *Client) GetUserAvatarThumbnails(userIDs []uint64, size ThumbnailSize, format ThumbnailFormat) ([]ThumbnailData, error) {
if len(userIDs) == 0 {
return []ThumbnailData{}, nil
}
if len(userIDs) > 100 {
return nil, GetError("batch size cannot exceed 100 users")
}
// Build request payload - the API expects an array directly, not wrapped in an object
requests := make([]ThumbnailRequest, len(userIDs))
for i, userID := range userIDs {
requests[i] = ThumbnailRequest{
Type: "AvatarHeadShot",
TargetID: userID,
Size: string(size),
Format: string(format),
}
}
jsonData, err := json.Marshal(requests)
if err != nil {
return nil, GetError("JSONMarshalError: " + err.Error())
}
req, err := http.NewRequest("POST", "https://thumbnails.roblox.com/v1/batch", bytes.NewBuffer(jsonData))
if err != nil {
return nil, GetError("RequestCreationError: " + err.Error())
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, GetError("RequestError: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, GetError(fmt.Sprintf("ResponseError: status code %d, body: %s", resp.StatusCode, string(body)))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, GetError("ReadBodyError: " + err.Error())
}
var response BatchThumbnailsResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, GetError("JSONUnmarshalError: " + err.Error())
}
return response.Data, nil
}

72
pkg/roblox/users.go Normal file
View File

@@ -0,0 +1,72 @@
package roblox
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// UserData represents a single user's information
type UserData struct {
ID uint64 `json:"id"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
}
// BatchUsersResponse represents the API response for batch user requests
type BatchUsersResponse struct {
Data []UserData `json:"data"`
}
// GetUsernames fetches usernames for multiple users in a single batch request
// Roblox allows up to 100 users per batch
func (c *Client) GetUsernames(userIDs []uint64) ([]UserData, error) {
if len(userIDs) == 0 {
return []UserData{}, nil
}
if len(userIDs) > 100 {
return nil, GetError("batch size cannot exceed 100 users")
}
// Build request payload
payload := map[string][]uint64{
"userIds": userIDs,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, GetError("JSONMarshalError: " + err.Error())
}
req, err := http.NewRequest("POST", "https://users.roblox.com/v1/users", bytes.NewBuffer(jsonData))
if err != nil {
return nil, GetError("RequestCreationError: " + err.Error())
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, GetError("RequestError: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, GetError(fmt.Sprintf("ResponseError: status code %d, body: %s", resp.StatusCode, string(body)))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, GetError("ReadBodyError: " + err.Error())
}
var response BatchUsersResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, GetError("JSONUnmarshalError: " + err.Error())
}
return response.Data, nil
}

View File

@@ -1,17 +1,22 @@
package service
import (
"context"
"git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"github.com/nats-io/nats.go"
"github.com/redis/go-redis/v9"
)
type Service struct {
db datastore.Datastore
nats nats.JetStreamContext
maps maps.MapsServiceClient
users users.UsersServiceClient
db datastore.Datastore
nats nats.JetStreamContext
maps maps.MapsServiceClient
users users.UsersServiceClient
thumbnailService *ThumbnailService
}
func NewService(
@@ -19,11 +24,44 @@ func NewService(
nats nats.JetStreamContext,
maps maps.MapsServiceClient,
users users.UsersServiceClient,
robloxClient *roblox.Client,
redisClient *redis.Client,
) Service {
return Service{
db: db,
nats: nats,
maps: maps,
users: users,
db: db,
nats: nats,
maps: maps,
users: users,
thumbnailService: NewThumbnailService(robloxClient, redisClient),
}
}
// GetAssetThumbnails proxies to the thumbnail service
func (s *Service) GetAssetThumbnails(ctx context.Context, assetIDs []uint64, size roblox.ThumbnailSize) (map[uint64]string, error) {
return s.thumbnailService.GetAssetThumbnails(ctx, assetIDs, size)
}
// GetUserAvatarThumbnails proxies to the thumbnail service
func (s *Service) GetUserAvatarThumbnails(ctx context.Context, userIDs []uint64, size roblox.ThumbnailSize) (map[uint64]string, error) {
return s.thumbnailService.GetUserAvatarThumbnails(ctx, userIDs, size)
}
// GetSingleAssetThumbnail proxies to the thumbnail service
func (s *Service) GetSingleAssetThumbnail(ctx context.Context, assetID uint64, size roblox.ThumbnailSize) (string, error) {
return s.thumbnailService.GetSingleAssetThumbnail(ctx, assetID, size)
}
// GetSingleUserAvatarThumbnail proxies to the thumbnail service
func (s *Service) GetSingleUserAvatarThumbnail(ctx context.Context, userID uint64, size roblox.ThumbnailSize) (string, error) {
return s.thumbnailService.GetSingleUserAvatarThumbnail(ctx, userID, size)
}
// GetUsernames proxies to the thumbnail service
func (s *Service) GetUsernames(ctx context.Context, userIDs []uint64) (map[uint64]string, error) {
return s.thumbnailService.GetUsernames(ctx, userIDs)
}
// GetSingleUsername proxies to the thumbnail service
func (s *Service) GetSingleUsername(ctx context.Context, userID uint64) (string, error) {
return s.thumbnailService.GetSingleUsername(ctx, userID)
}

218
pkg/service/thumbnails.go Normal file
View File

@@ -0,0 +1,218 @@
package service
import (
"context"
"encoding/json"
"fmt"
"time"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"github.com/redis/go-redis/v9"
)
type ThumbnailService struct {
robloxClient *roblox.Client
redisClient *redis.Client
cacheTTL time.Duration
}
func NewThumbnailService(robloxClient *roblox.Client, redisClient *redis.Client) *ThumbnailService {
return &ThumbnailService{
robloxClient: robloxClient,
redisClient: redisClient,
cacheTTL: 24 * time.Hour, // Cache thumbnails for 24 hours
}
}
// CachedThumbnail represents a cached thumbnail entry
type CachedThumbnail struct {
ImageURL string `json:"imageUrl"`
State string `json:"state"`
CachedAt time.Time `json:"cachedAt"`
}
// GetAssetThumbnails fetches thumbnails with Redis caching and batching
func (s *ThumbnailService) GetAssetThumbnails(ctx context.Context, assetIDs []uint64, size roblox.ThumbnailSize) (map[uint64]string, error) {
if len(assetIDs) == 0 {
return map[uint64]string{}, nil
}
result := make(map[uint64]string)
var missingIDs []uint64
// Try to get from cache first
for _, assetID := range assetIDs {
cacheKey := fmt.Sprintf("thumbnail:asset:%d:%s", assetID, size)
cached, err := s.redisClient.Get(ctx, cacheKey).Result()
if err == redis.Nil {
// Cache miss
missingIDs = append(missingIDs, assetID)
} else if err != nil {
// Redis error - treat as cache miss
missingIDs = append(missingIDs, assetID)
} else {
// Cache hit
var thumbnail CachedThumbnail
if err := json.Unmarshal([]byte(cached), &thumbnail); err == nil && thumbnail.State == "Completed" {
result[assetID] = thumbnail.ImageURL
} else {
missingIDs = append(missingIDs, assetID)
}
}
}
// If all were cached, return early
if len(missingIDs) == 0 {
return result, nil
}
// Batch fetch missing thumbnails from Roblox API
// Split into batches of 100 (Roblox API limit)
for i := 0; i < len(missingIDs); i += 100 {
end := i + 100
if end > len(missingIDs) {
end = len(missingIDs)
}
batch := missingIDs[i:end]
thumbnails, err := s.robloxClient.GetAssetThumbnails(batch, size, roblox.FormatPng)
if err != nil {
return nil, fmt.Errorf("failed to fetch thumbnails: %w", err)
}
// Process results and cache them
for _, thumb := range thumbnails {
cached := CachedThumbnail{
ImageURL: thumb.ImageURL,
State: thumb.State,
CachedAt: time.Now(),
}
if thumb.State == "Completed" && thumb.ImageURL != "" {
result[thumb.TargetID] = thumb.ImageURL
}
// Cache the result (even if incomplete, to avoid repeated API calls)
cacheKey := fmt.Sprintf("thumbnail:asset:%d:%s", thumb.TargetID, size)
cachedJSON, _ := json.Marshal(cached)
// Use shorter TTL for incomplete thumbnails
ttl := s.cacheTTL
if thumb.State != "Completed" {
ttl = 5 * time.Minute
}
s.redisClient.Set(ctx, cacheKey, cachedJSON, ttl)
}
}
return result, nil
}
// GetUserAvatarThumbnails fetches user avatar thumbnails with Redis caching and batching
func (s *ThumbnailService) GetUserAvatarThumbnails(ctx context.Context, userIDs []uint64, size roblox.ThumbnailSize) (map[uint64]string, error) {
if len(userIDs) == 0 {
return map[uint64]string{}, nil
}
result := make(map[uint64]string)
var missingIDs []uint64
// Try to get from cache first
for _, userID := range userIDs {
cacheKey := fmt.Sprintf("thumbnail:user:%d:%s", userID, size)
cached, err := s.redisClient.Get(ctx, cacheKey).Result()
if err == redis.Nil {
// Cache miss
missingIDs = append(missingIDs, userID)
} else if err != nil {
// Redis error - treat as cache miss
missingIDs = append(missingIDs, userID)
} else {
// Cache hit
var thumbnail CachedThumbnail
if err := json.Unmarshal([]byte(cached), &thumbnail); err == nil && thumbnail.State == "Completed" {
result[userID] = thumbnail.ImageURL
} else {
missingIDs = append(missingIDs, userID)
}
}
}
// If all were cached, return early
if len(missingIDs) == 0 {
return result, nil
}
// Batch fetch missing thumbnails from Roblox API
// Split into batches of 100 (Roblox API limit)
for i := 0; i < len(missingIDs); i += 100 {
end := i + 100
if end > len(missingIDs) {
end = len(missingIDs)
}
batch := missingIDs[i:end]
thumbnails, err := s.robloxClient.GetUserAvatarThumbnails(batch, size, roblox.FormatPng)
if err != nil {
return nil, fmt.Errorf("failed to fetch user thumbnails: %w", err)
}
// Process results and cache them
for _, thumb := range thumbnails {
cached := CachedThumbnail{
ImageURL: thumb.ImageURL,
State: thumb.State,
CachedAt: time.Now(),
}
if thumb.State == "Completed" && thumb.ImageURL != "" {
result[thumb.TargetID] = thumb.ImageURL
}
// Cache the result
cacheKey := fmt.Sprintf("thumbnail:user:%d:%s", thumb.TargetID, size)
cachedJSON, _ := json.Marshal(cached)
// Use shorter TTL for incomplete thumbnails
ttl := s.cacheTTL
if thumb.State != "Completed" {
ttl = 5 * time.Minute
}
s.redisClient.Set(ctx, cacheKey, cachedJSON, ttl)
}
}
return result, nil
}
// GetSingleAssetThumbnail is a convenience method for fetching a single asset thumbnail
func (s *ThumbnailService) GetSingleAssetThumbnail(ctx context.Context, assetID uint64, size roblox.ThumbnailSize) (string, error) {
results, err := s.GetAssetThumbnails(ctx, []uint64{assetID}, size)
if err != nil {
return "", err
}
if url, ok := results[assetID]; ok {
return url, nil
}
return "", fmt.Errorf("thumbnail not available for asset %d", assetID)
}
// GetSingleUserAvatarThumbnail is a convenience method for fetching a single user avatar thumbnail
func (s *ThumbnailService) GetSingleUserAvatarThumbnail(ctx context.Context, userID uint64, size roblox.ThumbnailSize) (string, error) {
results, err := s.GetUserAvatarThumbnails(ctx, []uint64{userID}, size)
if err != nil {
return "", err
}
if url, ok := results[userID]; ok {
return url, nil
}
return "", fmt.Errorf("thumbnail not available for user %d", userID)
}

108
pkg/service/users.go Normal file
View File

@@ -0,0 +1,108 @@
package service
import (
"context"
"encoding/json"
"fmt"
"time"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"github.com/redis/go-redis/v9"
)
// CachedUser represents a cached user entry
type CachedUser struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
CachedAt time.Time `json:"cachedAt"`
}
// GetUsernames fetches usernames with Redis caching and batching
func (s *ThumbnailService) GetUsernames(ctx context.Context, userIDs []uint64) (map[uint64]string, error) {
if len(userIDs) == 0 {
return map[uint64]string{}, nil
}
result := make(map[uint64]string)
var missingIDs []uint64
// Try to get from cache first
for _, userID := range userIDs {
cacheKey := fmt.Sprintf("user:name:%d", userID)
cached, err := s.redisClient.Get(ctx, cacheKey).Result()
if err == redis.Nil {
// Cache miss
missingIDs = append(missingIDs, userID)
} else if err != nil {
// Redis error - treat as cache miss
missingIDs = append(missingIDs, userID)
} else {
// Cache hit
var user CachedUser
if err := json.Unmarshal([]byte(cached), &user); err == nil && user.Name != "" {
result[userID] = user.Name
} else {
missingIDs = append(missingIDs, userID)
}
}
}
// If all were cached, return early
if len(missingIDs) == 0 {
return result, nil
}
// Batch fetch missing usernames from Roblox API
// Split into batches of 100 (Roblox API limit)
for i := 0; i < len(missingIDs); i += 100 {
end := i + 100
if end > len(missingIDs) {
end = len(missingIDs)
}
batch := missingIDs[i:end]
var users []roblox.UserData
var err error
users, err = s.robloxClient.GetUsernames(batch)
if err != nil {
return nil, fmt.Errorf("failed to fetch usernames: %w", err)
}
// Process results and cache them
for _, user := range users {
cached := CachedUser{
Name: user.Name,
DisplayName: user.DisplayName,
CachedAt: time.Now(),
}
if user.Name != "" {
result[user.ID] = user.Name
}
// Cache the result
cacheKey := fmt.Sprintf("user:name:%d", user.ID)
cachedJSON, _ := json.Marshal(cached)
// Cache usernames for a long time (7 days) since they rarely change
s.redisClient.Set(ctx, cacheKey, cachedJSON, 7*24*time.Hour)
}
}
return result, nil
}
// GetSingleUsername is a convenience method for fetching a single username
func (s *ThumbnailService) GetSingleUsername(ctx context.Context, userID uint64) (string, error) {
results, err := s.GetUsernames(ctx, []uint64{userID})
if err != nil {
return "", err
}
if name, ok := results[userID]; ok {
return name, nil
}
return "", fmt.Errorf("username not available for user %d", userID)
}

105
pkg/web_api/stats.go Normal file
View File

@@ -0,0 +1,105 @@
package web_api
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// GET /stats
func (svc *Service) GetStats(ctx context.Context) (*api.Stats, error) {
// Get total submissions count
totalSubmissions, _, err := svc.inner.ListSubmissionsWithTotal(ctx, service.NewSubmissionFilter(), model.Page{
Number: 1,
Size: 0, // We only want the count, not the items
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
// Get total mapfixes count
totalMapfixes, _, err := svc.inner.ListMapfixesWithTotal(ctx, service.NewMapfixFilter(), model.Page{
Number: 1,
Size: 0, // We only want the count, not the items
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
// Get released submissions count
releasedSubmissionsFilter := service.NewSubmissionFilter()
releasedSubmissionsFilter.SetStatuses([]model.SubmissionStatus{model.SubmissionStatusReleased})
releasedSubmissions, _, err := svc.inner.ListSubmissionsWithTotal(ctx, releasedSubmissionsFilter, model.Page{
Number: 1,
Size: 0,
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
// Get released mapfixes count
releasedMapfixesFilter := service.NewMapfixFilter()
releasedMapfixesFilter.SetStatuses([]model.MapfixStatus{model.MapfixStatusReleased})
releasedMapfixes, _, err := svc.inner.ListMapfixesWithTotal(ctx, releasedMapfixesFilter, model.Page{
Number: 1,
Size: 0,
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
// Get submitted submissions count (under review)
submittedSubmissionsFilter := service.NewSubmissionFilter()
submittedSubmissionsFilter.SetStatuses([]model.SubmissionStatus{
model.SubmissionStatusUnderConstruction,
model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitting,
model.SubmissionStatusSubmitted,
model.SubmissionStatusAcceptedUnvalidated,
model.SubmissionStatusValidating,
model.SubmissionStatusValidated,
model.SubmissionStatusUploading,
model.SubmissionStatusUploaded,
})
submittedSubmissions, _, err := svc.inner.ListSubmissionsWithTotal(ctx, submittedSubmissionsFilter, model.Page{
Number: 1,
Size: 0,
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
// Get submitted mapfixes count (under review)
submittedMapfixesFilter := service.NewMapfixFilter()
submittedMapfixesFilter.SetStatuses([]model.MapfixStatus{
model.MapfixStatusUnderConstruction,
model.MapfixStatusChangesRequested,
model.MapfixStatusSubmitting,
model.MapfixStatusSubmitted,
model.MapfixStatusAcceptedUnvalidated,
model.MapfixStatusValidating,
model.MapfixStatusValidated,
model.MapfixStatusUploading,
model.MapfixStatusUploaded,
model.MapfixStatusReleasing,
})
submittedMapfixes, _, err := svc.inner.ListMapfixesWithTotal(ctx, submittedMapfixesFilter, model.Page{
Number: 1,
Size: 0,
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
return &api.Stats{
TotalSubmissions: totalSubmissions,
TotalMapfixes: totalMapfixes,
ReleasedSubmissions: releasedSubmissions,
ReleasedMapfixes: releasedMapfixes,
SubmittedSubmissions: submittedSubmissions,
SubmittedMapfixes: submittedMapfixes,
}, nil
}

135
pkg/web_api/thumbnails.go Normal file
View File

@@ -0,0 +1,135 @@
package web_api
import (
"context"
"strconv"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
)
// BatchAssetThumbnails handles batch fetching of asset thumbnails
func (svc *Service) BatchAssetThumbnails(ctx context.Context, req *api.BatchAssetThumbnailsReq) (*api.BatchAssetThumbnailsOK, error) {
if len(req.AssetIds) == 0 {
return &api.BatchAssetThumbnailsOK{
Thumbnails: api.NewOptBatchAssetThumbnailsOKThumbnails(map[string]string{}),
}, nil
}
// Convert size string to enum
size := roblox.Size420x420
if req.Size.IsSet() {
sizeStr := req.Size.Value
switch api.BatchAssetThumbnailsReqSize(sizeStr) {
case api.BatchAssetThumbnailsReqSize150x150:
size = roblox.Size150x150
case api.BatchAssetThumbnailsReqSize768x432:
size = roblox.Size768x432
}
}
// Fetch thumbnails from service
thumbnails, err := svc.inner.GetAssetThumbnails(ctx, req.AssetIds, size)
if err != nil {
return nil, err
}
// Convert map[uint64]string to map[string]string for JSON
result := make(map[string]string, len(thumbnails))
for assetID, url := range thumbnails {
result[strconv.FormatUint(assetID, 10)] = url
}
return &api.BatchAssetThumbnailsOK{
Thumbnails: api.NewOptBatchAssetThumbnailsOKThumbnails(result),
}, nil
}
// GetAssetThumbnail handles single asset thumbnail fetch (with redirect)
func (svc *Service) GetAssetThumbnail(ctx context.Context, params api.GetAssetThumbnailParams) (*api.GetAssetThumbnailFound, error) {
// Convert size string to enum
size := roblox.Size420x420
if params.Size.IsSet() {
sizeStr := params.Size.Value
switch api.GetAssetThumbnailSize(sizeStr) {
case api.GetAssetThumbnailSize150x150:
size = roblox.Size150x150
case api.GetAssetThumbnailSize768x432:
size = roblox.Size768x432
}
}
// Fetch thumbnail
thumbnailURL, err := svc.inner.GetSingleAssetThumbnail(ctx, params.AssetID, size)
if err != nil {
return nil, err
}
// Return redirect response
return &api.GetAssetThumbnailFound{
Location: api.NewOptString(thumbnailURL),
}, nil
}
// BatchUserThumbnails handles batch fetching of user avatar thumbnails
func (svc *Service) BatchUserThumbnails(ctx context.Context, req *api.BatchUserThumbnailsReq) (*api.BatchUserThumbnailsOK, error) {
if len(req.UserIds) == 0 {
return &api.BatchUserThumbnailsOK{
Thumbnails: api.NewOptBatchUserThumbnailsOKThumbnails(map[string]string{}),
}, nil
}
// Convert size string to enum
size := roblox.Size150x150
if req.Size.IsSet() {
sizeStr := req.Size.Value
switch api.BatchUserThumbnailsReqSize(sizeStr) {
case api.BatchUserThumbnailsReqSize420x420:
size = roblox.Size420x420
case api.BatchUserThumbnailsReqSize768x432:
size = roblox.Size768x432
}
}
// Fetch thumbnails from service
thumbnails, err := svc.inner.GetUserAvatarThumbnails(ctx, req.UserIds, size)
if err != nil {
return nil, err
}
// Convert map[uint64]string to map[string]string for JSON
result := make(map[string]string, len(thumbnails))
for userID, url := range thumbnails {
result[strconv.FormatUint(userID, 10)] = url
}
return &api.BatchUserThumbnailsOK{
Thumbnails: api.NewOptBatchUserThumbnailsOKThumbnails(result),
}, nil
}
// GetUserThumbnail handles single user avatar thumbnail fetch (with redirect)
func (svc *Service) GetUserThumbnail(ctx context.Context, params api.GetUserThumbnailParams) (*api.GetUserThumbnailFound, error) {
// Convert size string to enum
size := roblox.Size150x150
if params.Size.IsSet() {
sizeStr := params.Size.Value
switch api.GetUserThumbnailSize(sizeStr) {
case api.GetUserThumbnailSize420x420:
size = roblox.Size420x420
case api.GetUserThumbnailSize768x432:
size = roblox.Size768x432
}
}
// Fetch thumbnail
thumbnailURL, err := svc.inner.GetSingleUserAvatarThumbnail(ctx, params.UserID, size)
if err != nil {
return nil, err
}
// Return redirect response
return &api.GetUserThumbnailFound{
Location: api.NewOptString(thumbnailURL),
}, nil
}

33
pkg/web_api/users.go Normal file
View File

@@ -0,0 +1,33 @@
package web_api
import (
"context"
"strconv"
"git.itzana.me/strafesnet/maps-service/pkg/api"
)
// BatchUsernames handles batch fetching of usernames
func (svc *Service) BatchUsernames(ctx context.Context, req *api.BatchUsernamesReq) (*api.BatchUsernamesOK, error) {
if len(req.UserIds) == 0 {
return &api.BatchUsernamesOK{
Usernames: api.NewOptBatchUsernamesOKUsernames(map[string]string{}),
}, nil
}
// Fetch usernames from service
usernames, err := svc.inner.GetUsernames(ctx, req.UserIds)
if err != nil {
return nil, err
}
// Convert map[uint64]string to map[string]string for JSON
result := make(map[string]string, len(usernames))
for userID, username := range usernames {
result[strconv.FormatUint(userID, 10)] = username
}
return &api.BatchUsernamesOK{
Usernames: api.NewOptBatchUsernamesOKUsernames(result),
}, nil
}

View File

@@ -1,6 +1,6 @@
[package]
name = "submissions-api"
version = "0.10.0"
version = "0.10.1"
edition = "2024"
publish = ["strafesnet"]
repository = "https://git.itzana.me/StrafesNET/maps-service"

View File

@@ -263,6 +263,36 @@ impl Context{
).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_mapfix_audit_events(&self,config:GetMapfixAuditEventsRequest)->Result<Vec<AuditEventReponse>,Error>{
let url_raw=format!("{}/mapfixes/{}/audit-events",self.0.base_url,config.MapfixID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
let mut query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_submission_audit_events(&self,config:GetSubmissionAuditEventsRequest)->Result<Vec<AuditEventReponse>,Error>{
let url_raw=format!("{}/submissions/{}/audit-events",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
let mut query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::ReqwestJson)
}
pub async fn release_submissions(&self,config:ReleaseRequest<'_>)->Result<OperationIDResponse,Error>{
let url_raw=format!("{}/release-submissions",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;

View File

@@ -30,7 +30,6 @@ impl<Items> std::error::Error for SingleItemError<Items> where Items:std::fmt::D
pub type ScriptSingleItemError=SingleItemError<Vec<ScriptID>>;
pub type ScriptPolicySingleItemError=SingleItemError<Vec<ScriptPolicyID>>;
#[allow(dead_code)]
#[derive(Debug)]
pub struct UrlAndBody{
pub url:url::Url,
@@ -76,7 +75,7 @@ pub enum GameID{
FlyTrials=5,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateMapfixRequest<'a>{
pub OperationID:OperationID,
@@ -89,13 +88,13 @@ pub struct CreateMapfixRequest<'a>{
pub TargetAssetID:u64,
pub Description:&'a str,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct MapfixIDResponse{
pub MapfixID:MapfixID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateSubmissionRequest<'a>{
pub OperationID:OperationID,
@@ -108,7 +107,7 @@ pub struct CreateSubmissionRequest<'a>{
pub Status:u32,
pub Roles:u32,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct SubmissionIDResponse{
pub SubmissionID:SubmissionID,
@@ -127,11 +126,11 @@ pub enum ResourceType{
Submission=2,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptsRequest<'a>{
pub Page:u32,
@@ -151,7 +150,7 @@ pub struct GetScriptsRequest<'a>{
pub struct HashRequest<'a>{
pub hash:&'a str,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptResponse{
pub ID:ScriptID,
@@ -161,7 +160,7 @@ pub struct ScriptResponse{
pub ResourceType:ResourceType,
pub ResourceID:ResourceID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptRequest<'a>{
pub Name:&'a str,
@@ -170,7 +169,7 @@ pub struct CreateScriptRequest<'a>{
#[serde(skip_serializing_if="Option::is_none")]
pub ResourceID:Option<ResourceID>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptIDResponse{
pub ScriptID:ScriptID,
@@ -186,11 +185,11 @@ pub enum Policy{
Replace=4,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct GetScriptPolicyRequest{
pub ScriptPolicyID:ScriptPolicyID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptPoliciesRequest<'a>{
pub Page:u32,
@@ -202,7 +201,7 @@ pub struct GetScriptPoliciesRequest<'a>{
#[serde(skip_serializing_if="Option::is_none")]
pub Policy:Option<Policy>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyResponse{
pub ID:ScriptPolicyID,
@@ -210,20 +209,20 @@ pub struct ScriptPolicyResponse{
pub ToScriptID:ScriptID,
pub Policy:Policy
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptPolicyRequest{
pub FromScriptID:ScriptID,
pub ToScriptID:ScriptID,
pub Policy:Policy,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyIDResponse{
pub ScriptPolicyID:ScriptPolicyID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct UpdateScriptPolicyRequest{
pub ID:ScriptPolicyID,
@@ -235,7 +234,7 @@ pub struct UpdateScriptPolicyRequest{
pub Policy:Option<Policy>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct UpdateSubmissionModelRequest{
pub SubmissionID:SubmissionID,
@@ -276,7 +275,7 @@ pub enum MapfixStatus{
Released=10,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetMapfixesRequest<'a>{
pub Page:u32,
@@ -292,7 +291,7 @@ pub struct GetMapfixesRequest<'a>{
pub StatusID:Option<MapfixStatus>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize,serde::Deserialize)]
pub struct MapfixResponse{
pub ID:MapfixID,
@@ -312,7 +311,7 @@ pub struct MapfixResponse{
pub Description:String,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct MapfixesResponse{
pub Total:u64,
@@ -342,7 +341,7 @@ pub enum SubmissionStatus{
Released=10,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetSubmissionsRequest<'a>{
pub Page:u32,
@@ -358,7 +357,7 @@ pub struct GetSubmissionsRequest<'a>{
pub StatusID:Option<SubmissionStatus>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct SubmissionResponse{
pub ID:SubmissionID,
@@ -376,14 +375,14 @@ pub struct SubmissionResponse{
pub StatusID:SubmissionStatus,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct SubmissionsResponse{
pub Total:u64,
pub Submissions:Vec<SubmissionResponse>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetMapsRequest<'a>{
pub Page:u32,
@@ -394,7 +393,7 @@ pub struct GetMapsRequest<'a>{
pub GameID:Option<GameID>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct MapResponse{
pub ID:i64,
@@ -404,7 +403,119 @@ pub struct MapResponse{
pub Date:i64,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetMapfixAuditEventsRequest{
pub Page:u32,
pub Limit:u32,
pub MapfixID:i64,
}
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct GetSubmissionAuditEventsRequest{
pub Page:u32,
pub Limit:u32,
pub SubmissionID:i64,
}
#[derive(Clone,Debug,serde_repr::Deserialize_repr)]
#[repr(u32)]
pub enum AuditEventType{
Action=0,
Comment=1,
ChangeModel=2,
ChangeValidatedModel=3,
ChangeDisplayName=4,
ChangeCreator=5,
Error=6,
CheckList=7,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventAction{
pub target_status:MapfixStatus,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventComment{
pub comment:String,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventChangeModel{
pub old_model_id:u64,
pub old_model_version:u64,
pub new_model_id:u64,
pub new_model_version:u64,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventChangeValidatedModel{
pub validated_model_id:u64,
pub validated_model_version:u64,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventChangeName{
pub old_name:String,
pub new_name:String,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventError{
pub error:String,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventCheck{
pub name:String,
pub summary:String,
pub passed:bool,
}
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventCheckList{
pub check_list:Vec<AuditEventCheck>,
}
#[derive(Clone,Debug)]
pub enum AuditEventData{
Action(AuditEventAction),
Comment(AuditEventComment),
ChangeModel(AuditEventChangeModel),
ChangeValidatedModel(AuditEventChangeValidatedModel),
ChangeDisplayName(AuditEventChangeName),
ChangeCreator(AuditEventChangeName),
Error(AuditEventError),
CheckList(AuditEventCheckList),
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
pub struct AuditEventID(pub(crate)i64);
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct AuditEventReponse{
pub ID:AuditEventID,
pub Date:i64,
pub User:u64,
pub Username:String,
pub ResourceType:ResourceType,
pub ResourceID:ResourceID,
pub EventType:AuditEventType,
EventData:serde_json::Value,
}
impl AuditEventReponse{
pub fn data(self)->serde_json::Result<AuditEventData>{
Ok(match self.EventType{
AuditEventType::Action=>AuditEventData::Action(serde_json::from_value(self.EventData)?),
AuditEventType::Comment=>AuditEventData::Comment(serde_json::from_value(self.EventData)?),
AuditEventType::ChangeModel=>AuditEventData::ChangeModel(serde_json::from_value(self.EventData)?),
AuditEventType::ChangeValidatedModel=>AuditEventData::ChangeValidatedModel(serde_json::from_value(self.EventData)?),
AuditEventType::ChangeDisplayName=>AuditEventData::ChangeDisplayName(serde_json::from_value(self.EventData)?),
AuditEventType::ChangeCreator=>AuditEventData::ChangeCreator(serde_json::from_value(self.EventData)?),
AuditEventType::Error=>AuditEventData::Error(serde_json::from_value(self.EventData)?),
AuditEventType::CheckList=>AuditEventData::CheckList(serde_json::from_value(self.EventData)?),
})
}
}
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct Check{
pub Name:&'static str,
@@ -412,7 +523,7 @@ pub struct Check{
pub Passed:bool,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionSubmittedRequest{
pub SubmissionID:SubmissionID,
@@ -422,33 +533,33 @@ pub struct ActionSubmissionSubmittedRequest{
pub GameID:GameID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionRequestChangesRequest{
pub SubmissionID:SubmissionID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionUploadedRequest{
pub SubmissionID:SubmissionID,
pub UploadedAssetID:u64,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionAcceptedRequest{
pub SubmissionID:SubmissionID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct CreateSubmissionAuditErrorRequest{
pub SubmissionID:SubmissionID,
pub ErrorMessage:String,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct CreateSubmissionAuditCheckListRequest<'a>{
pub SubmissionID:SubmissionID,
@@ -457,8 +568,16 @@ pub struct CreateSubmissionAuditCheckListRequest<'a>{
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
pub struct SubmissionID(pub(crate)i64);
impl SubmissionID{
pub const fn new(value:i64)->Self{
Self(value)
}
pub const fn value(&self)->i64{
self.0
}
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct UpdateMapfixModelRequest{
pub MapfixID:MapfixID,
@@ -466,7 +585,7 @@ pub struct UpdateMapfixModelRequest{
pub ModelVersion:u64,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixSubmittedRequest{
pub MapfixID:MapfixID,
@@ -476,32 +595,32 @@ pub struct ActionMapfixSubmittedRequest{
pub GameID:GameID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixRequestChangesRequest{
pub MapfixID:MapfixID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixUploadedRequest{
pub MapfixID:MapfixID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionMapfixAcceptedRequest{
pub MapfixID:MapfixID,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct CreateMapfixAuditErrorRequest{
pub MapfixID:MapfixID,
pub ErrorMessage:String,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct CreateMapfixAuditCheckListRequest<'a>{
pub MapfixID:MapfixID,
@@ -510,8 +629,16 @@ pub struct CreateMapfixAuditCheckListRequest<'a>{
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq,serde::Serialize,serde::Deserialize)]
pub struct MapfixID(pub(crate)i64);
impl MapfixID{
pub const fn new(value:i64)->Self{
Self(value)
}
pub const fn value(&self)->i64{
self.0
}
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionOperationFailedRequest{
pub OperationID:OperationID,
@@ -538,7 +665,7 @@ impl Resource{
}
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct ReleaseInfo{
pub SubmissionID:SubmissionID,
@@ -548,7 +675,7 @@ pub struct ReleaseInfo{
pub struct ReleaseRequest<'a>{
pub schedule:&'a [ReleaseInfo],
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
pub struct OperationIDResponse{
pub OperationID:OperationID,

View File

@@ -6,7 +6,7 @@ use heck::{ToSnakeCase,ToTitleCase};
use rbx_dom_weak::Instance;
use rust_grpc::validator::Check;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ModelInfoDownload(rbx_asset::cloud::GetError),
@@ -33,7 +33,7 @@ macro_rules! lazy_regex{
}};
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct CheckRequest{
ModelID:u64,
SkipChecks:bool,
@@ -79,7 +79,7 @@ struct ModeElement{
zone:Zone,
mode_id:ModeID,
}
#[allow(dead_code)]
#[expect(dead_code)]
pub enum IDParseError{
NoCaptures,
ParseInt(core::num::ParseIntError),
@@ -442,7 +442,7 @@ pub struct MapInfoOwned{
pub creator:String,
pub game_id:GameID,
}
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum IntoMapInfoOwnedError{
DisplayName(StringValueError),

View File

@@ -1,7 +1,7 @@
use crate::check::CheckListAndVersion;
use crate::nats_types::CheckMapfixRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
Check(crate::check::Error),

View File

@@ -1,7 +1,7 @@
use crate::check::CheckListAndVersion;
use crate::nats_types::CheckSubmissionRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
Check(crate::check::Error),

View File

@@ -1,7 +1,7 @@
use crate::download::download_asset_version;
use crate::rbx_util::{get_root_instance,get_mapinfo,read_dom,MapInfo,ReadDomError,GetRootInstanceError,GameID};
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
CreatorTypeMustBeUser,
@@ -17,11 +17,11 @@ impl std::fmt::Display for Error{
}
impl std::error::Error for Error{}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct CreateRequest{
pub ModelID:u64,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct CreateResult{
pub AssetOwner:u64,
pub DisplayName:Option<String>,

View File

@@ -1,7 +1,7 @@
use crate::nats_types::CreateMapfixRequest;
use crate::create::CreateRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
Create(crate::create::Error),

View File

@@ -2,7 +2,7 @@ use crate::nats_types::CreateSubmissionRequest;
use crate::create::CreateRequest;
use crate::rbx_util::GameID;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
Create(crate::create::Error),

View File

@@ -1,4 +1,4 @@
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ModelLocationDownload(rbx_asset::cloud::GetError),

View File

@@ -22,7 +22,6 @@ mod validator;
mod validate_mapfix;
mod validate_submission;
#[allow(dead_code)]
#[derive(Debug)]
pub enum StartupError{
API(tonic::transport::Error),

View File

@@ -1,4 +1,4 @@
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum HandleMessageError{
Messages(async_nats::jetstream::consumer::pull::MessagesError),

View File

@@ -4,7 +4,7 @@
// Requests are sent from maps-service to validator
// Validation invokes the REST api to update the submissions
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CreateSubmissionRequest{
// operation_id is passed back in the response message
@@ -18,7 +18,7 @@ pub struct CreateSubmissionRequest{
pub Roles:u32,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CreateMapfixRequest{
pub OperationID:u32,
@@ -27,7 +27,7 @@ pub struct CreateMapfixRequest{
pub Description:String,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CheckSubmissionRequest{
pub SubmissionID:u64,
@@ -35,7 +35,7 @@ pub struct CheckSubmissionRequest{
pub SkipChecks:bool,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct CheckMapfixRequest{
pub MapfixID:u64,
@@ -43,7 +43,7 @@ pub struct CheckMapfixRequest{
pub SkipChecks:bool,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ValidateSubmissionRequest{
// submission_id is passed back in the response message
@@ -53,7 +53,7 @@ pub struct ValidateSubmissionRequest{
pub ValidatedModelID:Option<u64>,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ValidateMapfixRequest{
// submission_id is passed back in the response message
@@ -64,7 +64,7 @@ pub struct ValidateMapfixRequest{
}
// Create a new map
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct UploadSubmissionRequest{
pub SubmissionID:u64,
@@ -73,7 +73,7 @@ pub struct UploadSubmissionRequest{
pub ModelName:String,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct UploadMapfixRequest{
pub MapfixID:u64,
@@ -83,7 +83,7 @@ pub struct UploadMapfixRequest{
}
// Release a new map
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseSubmissionRequest{
pub SubmissionID:u64,
@@ -97,14 +97,14 @@ pub struct ReleaseSubmissionRequest{
pub Submitter:u64,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseSubmissionsBatchRequest{
pub Submissions:Vec<ReleaseSubmissionRequest>,
pub OperationID:u32,
}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ReleaseMapfixRequest{
pub MapfixID:u64,

View File

@@ -1,4 +1,4 @@
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum ReadDomError{
Binary(rbx_binary::DecodeError),

View File

@@ -183,7 +183,7 @@ async fn release_inner(
Ok(())
}
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
UpdateOperation(tonic::Status),

View File

@@ -1,7 +1,7 @@
use crate::download::download_asset_version;
use crate::nats_types::UploadMapfixRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum InnerError{
Download(crate::download::Error),
@@ -43,7 +43,7 @@ async fn upload_inner(
Ok(())
}
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionMapfixUploaded(tonic::Status),

View File

@@ -1,7 +1,7 @@
use crate::download::download_asset_version;
use crate::nats_types::UploadSubmissionRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum InnerError{
Download(crate::download::Error),
@@ -44,7 +44,7 @@ async fn upload_inner(
Ok(upload_response.AssetId)
}
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionSubmissionUploaded(tonic::Status),

View File

@@ -1,6 +1,6 @@
use crate::nats_types::ValidateMapfixRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionMapfixValidate(tonic::Status),

View File

@@ -1,6 +1,6 @@
use crate::nats_types::ValidateSubmissionRequest;
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ApiActionSubmissionValidate(tonic::Status),

View File

@@ -17,7 +17,7 @@ fn hash_source(source:&str)->u64{
std::hash::Hasher::finish(&hasher)
}
#[allow(dead_code)]
#[expect(dead_code)]
#[derive(Debug)]
pub enum Error{
ModelInfoDownload(rbx_asset::cloud::GetError),
@@ -52,7 +52,7 @@ impl std::fmt::Display for Error{
}
impl std::error::Error for Error{}
#[allow(nonstandard_style)]
#[expect(nonstandard_style)]
pub struct ValidateRequest{
pub ModelID:u64,
pub ModelVersion:u64,

34
web/.gitignore vendored
View File

@@ -1,24 +1,12 @@
bun.lockb
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
/dist
# misc
.DS_Store
@@ -29,12 +17,22 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# env files (can opt-in for committing if needed)
# env files
.env*
# vercel
.vercel
.env.local
.env.development.local
.env.test.local
.env.production.local
# typescript
*.tsbuildinfo
next-env.d.ts
# editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,13 +1,29 @@
FROM registry.itzana.me/docker-proxy/oven/bun:1.3.3
# Build stage
FROM registry.itzana.me/docker-proxy/oven/bun:1.3.3 AS builder
WORKDIR /app
COPY package.json bun.lockb* ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Release
FROM registry.itzana.me/docker-proxy/nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
# Add nginx configuration for SPA routing
RUN echo 'server { \
listen 3000; \
location / { \
root /usr/share/nginx/html; \
index index.html; \
try_files $uri $uri/ /index.html; \
} \
}' > /etc/nginx/conf.d/default.conf
EXPOSE 3000
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun install
RUN bun run build
ENTRYPOINT ["bun", "run", "start"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -9,10 +9,11 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
"@tanstack/react-query": "^5.90.12",
"date-fns": "^4.1.0",
"next": "^16.0.7",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-router-dom": "^7.1.3",
"sass": "^1.94.2",
},
"devDependencies": {
@@ -20,9 +21,10 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.39.1",
"eslint-config-next": "16.0.7",
"typescript": "^5.9.3",
"vite": "^6.0.7",
},
},
},
@@ -43,6 +45,8 @@
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
@@ -51,7 +55,11 @@
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
"@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
"@babel/runtime": ["@babel/runtime@7.27.0", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw=="],
@@ -61,12 +69,6 @@
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@emnapi/core": ["@emnapi/core@1.4.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.1", "tslib": "^2.4.0" } }, "sha512-H+N/FqT07NmLmt6OFFtDfwe8PNygprzBikrEMyQfgqSmT0vzE515Pz7R8izwB9q/zsH/MA64AKoul3sA6/CzVg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw=="],
"@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="],
"@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="],
@@ -93,6 +95,58 @@
"@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
@@ -119,56 +173,6 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="],
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -197,36 +201,6 @@
"@mui/utils": ["@mui/utils@7.3.6", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@mui/types": "^7.4.9", "@types/prop-types": "^15.7.15", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-is": "^19.2.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-jn+Ba02O6PiFs7nKva8R2aJJ9kJC+3kQ2R0BbKNY3KQQ36Qng98GnPRFTlbwYTdMD6hLEBKaMLUktyg/rTfd2w=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.8", "", { "dependencies": { "@emnapi/core": "^1.4.0", "@emnapi/runtime": "^1.4.0", "@tybys/wasm-util": "^0.9.0" } }, "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg=="],
"@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.0.7", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-hFrTNZcMEG+k7qxVxZJq3F32Kms130FAhG8lvw2zkKBgAcNOJIxlljNiCjGygvBshvaGBdf88q2CqWtnqezDHA=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
@@ -257,18 +231,68 @@
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
@@ -281,55 +305,7 @@
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="],
"@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EpRILdWr3/xDa/7MoyfO7JuBIJqpBMphtu4+80BK1bRfFcniVT74h3Z7q1+WOc92FuIAYatB1vn9TJR67sORGw=="],
"@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-ntj/g7lPyqwinMJWZ+DKHBse8HhVxswGTmNgFKJtdgGub3M3zp5BSZ3bvMP+kBT6dnYJLSVlDqdwOq1P8i0+/g=="],
"@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-l6BT8f2CU821EW7U8hSUK8XPq4bmyTlt9Mn4ERrfjJNoCw0/JoHAh9amZZtV3cwC3bwwIat+GUnrcHTG9+qixw=="],
"@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-8ScEc5a4y7oE2BonRvzJ+2GSkBaYWyh0/Ko4Q25e/ix6ANpJNhwEPZvCR6GVRmsQAYMIfQvYLdM6YEN+qRjnAQ=="],
"@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-8qQ6l1VTzLNd3xb2IEXISOKwMGXDCzY/UNy/7SovFW2Sp0K3YbL7Ao7R18v6SQkLqQlhhqSBIFRk+u6+qu5R5A=="],
"@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-v81R2wjqcWXJlQY23byqYHt9221h4anQ6wwN64oMD/WAE+FmxPHFZee5bhRkNVtzqO/q7wki33VFWlhiADwUeQ=="],
"@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-cAOx/j0u5coMg4oct/BwMzvWJdVciVauUvsd+GQB/1FZYKQZmqPy0EjJzJGbVzFc6gbnfEcSqvQE6gvbGf2N8Q=="],
"@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-mq2blqwErgDJD4gtFDlTX/HZ7lNP8YCHYFij2gkXPtMzrXxPW1hOtxL6xg4NWxvnj4bppppb0W3s/buvM55yfg=="],
"@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.3.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-u0VRzfFYysarYHnztj2k2xr+eu9rmgoTUUgCCIT37Nr+j0A05Xk2c3RY8Mh5+DhCl2aYibihnaAEJHeR0UOFIQ=="],
"@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-OrVo5ZsG29kBF0Ug95a2KidS16PqAMmQNozM6InbquOfW/udouk063e25JVLqIBhHLB2WyBnixOQ19tmeC/hIg=="],
"@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-PYnmrwZ4HMp9SkrOhqPghY/aoL+Rtd4CQbr93GlrRTjK6kDzfMfgz3UH3jt6elrQAfupa1qyr1uXzeVmoEAxUA=="],
"@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.3.3", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.7" }, "cpu": "none" }, "sha512-81AnQY6fShmktQw4hWDUIilsKSdvr/acdJ5azAreu2IWNlaJOKphJSsUVWE+yCk6kBMoQyG9ZHCb/krb5K0PEA=="],
"@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-X/42BMNw7cW6xrB9syuP5RusRnWGoq+IqvJO8IDpp/BZg64J1uuIW6qA/1Cl13Y4LyLXbJVYbYNSKwR/FiHEng=="],
"@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.3.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-EGNnNGQxMU5aTN7js3ETYvuw882zcO+dsVjs+DwO2j/fRVKth87C8e2GzxW1L3+iWAXMyJhvFBKRavk9Og1Z6A=="],
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-GraLbYqOJcmW1qY3osB+2YIiD62nVf2/bVLHZmrb4t/YSUwE03l7TwcDJl08T/Tm3SVhepX8RQkpzWbag/Sb4w=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
@@ -341,34 +317,6 @@
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
"array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
"array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
"array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="],
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -381,12 +329,6 @@
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="],
@@ -395,8 +337,6 @@
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -405,7 +345,9 @@
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
@@ -413,53 +355,21 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"electron-to-chromium": ["electron-to-chromium@1.5.266", "", {}, "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
"es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -467,22 +377,6 @@
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
"eslint-config-next": ["eslint-config-next@16.0.7", "", { "dependencies": { "@next/eslint-plugin-next": "16.0.7", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-WubFGLFHfk2KivkdRGfx6cGSFhaQqhERRfyO8BRx+qiGPGp7WLKcPvYC4mdx1z3VhVRcrfFzczjjTrbJZOpnEQ=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.0", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.12", "unrs-resolver": "^1.3.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-aV3/dVsT0/H9BtpNwbaqvl+0xGMRGzncLyhm793NFGvbwGGvzyAykqWZ8oZlZuGwuHkwJjhWJkG1cM3ynvd2pQ=="],
"eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="],
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
"eslint-plugin-react": ["eslint-plugin-react@7.37.4", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
@@ -499,15 +393,11 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
@@ -521,52 +411,20 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
"has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -577,68 +435,18 @@
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
"is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="],
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
"is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -653,16 +461,10 @@
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
"language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
@@ -675,48 +477,24 @@
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"next": ["next@16.0.7", "", { "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.7", "@next/swc-darwin-x64": "16.0.7", "@next/swc-linux-arm64-gnu": "16.0.7", "@next/swc-linux-arm64-musl": "16.0.7", "@next/swc-linux-x64-gnu": "16.0.7", "@next/swc-linux-x64-musl": "16.0.7", "@next/swc-win32-arm64-msvc": "16.0.7", "@next/swc-win32-x64-msvc": "16.0.7", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
"object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
"object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
"object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
@@ -735,11 +513,9 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@@ -747,39 +523,29 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
"react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
"react-is": ["react-is@19.2.1", "", {}, "sha512-L7BnWgRbMwzMAubQcS7sXdPdNLmKlucPlopgAzx7FtYbksWZgEWiuYM5x9T6UqS2Ne0rsgQTq5kY2SGqpzUkYA=="],
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
"react-router": ["react-router@7.11.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ=="],
"react-router-dom": ["react-router-dom@7.11.0", "", { "dependencies": { "react-router": "7.11.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g=="],
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="],
"sass": ["sass@1.94.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A=="],
@@ -787,102 +553,42 @@
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
"string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
"string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="],
"string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
"string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"tinyglobby": ["tinyglobby@0.2.12", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
"typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"unrs-resolver": ["unrs-resolver@1.3.3", "", { "optionalDependencies": { "@unrs/resolver-binding-darwin-arm64": "1.3.3", "@unrs/resolver-binding-darwin-x64": "1.3.3", "@unrs/resolver-binding-freebsd-x64": "1.3.3", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.3.3", "@unrs/resolver-binding-linux-arm-musleabihf": "1.3.3", "@unrs/resolver-binding-linux-arm64-gnu": "1.3.3", "@unrs/resolver-binding-linux-arm64-musl": "1.3.3", "@unrs/resolver-binding-linux-ppc64-gnu": "1.3.3", "@unrs/resolver-binding-linux-s390x-gnu": "1.3.3", "@unrs/resolver-binding-linux-x64-gnu": "1.3.3", "@unrs/resolver-binding-linux-x64-musl": "1.3.3", "@unrs/resolver-binding-wasm32-wasi": "1.3.3", "@unrs/resolver-binding-win32-arm64-msvc": "1.3.3", "@unrs/resolver-binding-win32-ia32-msvc": "1.3.3", "@unrs/resolver-binding-win32-x64-msvc": "1.3.3" } }, "sha512-PFLAGQzYlyjniXdbmQ3dnGMZJXX5yrl2YS4DLRfR3BhgUsE1zpRIrccp9XMOGRfIHpdFvCn/nr5N1KMVda4x3A=="],
"update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
"which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -891,29 +597,13 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"@babel/core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"@babel/generator/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="],
"@babel/helper-module-imports/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
"@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="],
"@babel/template/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"@emotion/serialize/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
@@ -939,86 +629,28 @@
"@mui/utils/@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.4.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw=="],
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"array.prototype.findlast/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"array.prototype.findlastindex/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"array.prototype.flat/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"array.prototype.flatmap/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"array.prototype.tosorted/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"arraybuffer.prototype.slice/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"dom-helpers/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"es-iterator-helpers/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"eslint/espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-jsx-a11y/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
"eslint-plugin-react/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"is-bun-module/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"jsx-ast-utils/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"object.fromentries/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"object.groupby/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"parse-json/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"reflect.getprototypeof/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"string.prototype.includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"string.prototype.matchall/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"string.prototype.repeat/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"string.prototype.trim/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@babel/helper-module-imports/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
"@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
"@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="],
"@babel/helper-module-imports/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
@@ -1027,24 +659,8 @@
"@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"@typescript-eslint/typescript-estree/tinyglobby/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"@typescript-eslint/typescript-estree/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"eslint-plugin-jsx-a11y/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"eslint-plugin-react/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"eslint/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"jsx-ast-utils/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
"parse-json/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@babel/helper-module-imports/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],

13
web/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Maps Service</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,16 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
distDir: "build",
output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**.rbxcdn.com",
},
],
},
};
export default nextConfig;

4078
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,21 +2,23 @@
"name": "map-service-web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev -p 3000 --turbopack",
"build": "next build",
"start": "next start -p 3000",
"lint": "next lint"
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --ext ts,tsx"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
"@tanstack/react-query": "^5.90.12",
"date-fns": "^4.1.0",
"next": "^16.0.7",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-router-dom": "^7.1.3",
"sass": "^1.94.2"
},
"devDependencies": {
@@ -24,8 +26,9 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.39.1",
"eslint-config-next": "16.0.7",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"vite": "^6.0.7"
}
}

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

40
web/src/App.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { Routes, Route } from 'react-router-dom'
import { ThemeProvider } from '@mui/material'
import { theme } from '@/app/lib/theme'
// Pages
import Home from '@/app/page'
import MapsPage from '@/app/maps/page'
import MapDetailPage from '@/app/maps/[mapId]/page'
import MapFixCreatePage from '@/app/maps/[mapId]/fix/page'
import MapfixesPage from '@/app/mapfixes/page'
import MapfixDetailPage from '@/app/mapfixes/[mapfixId]/page'
import SubmissionsPage from '@/app/submissions/page'
import SubmissionDetailPage from '@/app/submissions/[submissionId]/page'
import SubmitPage from '@/app/submit/page'
import AdminSubmitPage from '@/app/admin-submit/page'
import OperationPage from '@/app/operations/[operationId]/page'
import NotFound from '@/app/not-found/page'
function App() {
return (
<ThemeProvider theme={theme}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/maps" element={<MapsPage />} />
<Route path="/maps/:mapId" element={<MapDetailPage />} />
<Route path="/maps/:mapId/fix" element={<MapFixCreatePage />} />
<Route path="/mapfixes" element={<MapfixesPage />} />
<Route path="/mapfixes/:mapfixId" element={<MapfixDetailPage />} />
<Route path="/submissions" element={<SubmissionsPage />} />
<Route path="/submissions/:submissionId" element={<SubmissionDetailPage />} />
<Route path="/submit" element={<SubmitPage />} />
<Route path="/admin-submit" element={<AdminSubmitPage />} />
<Route path="/operations/:operationId" element={<OperationPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</ThemeProvider>
)
}
export default App

View File

@@ -1,6 +1,6 @@
import {Box, IconButton, Typography} from "@mui/material";
import {Box, Button, IconButton, Typography} from "@mui/material";
import {useEffect, useRef, useState} from "react";
import Link from "next/link";
import { Link } from "react-router-dom";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import {SubmissionInfo} from "@/app/ts/Submission";
@@ -65,14 +65,22 @@ export function Carousel<T extends CarouselItem>({ title, items, renderItem, vie
return (
<Box mb={6}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Typography variant="h4" component="h2" fontWeight="bold">
{title}
</Typography>
<Link href={viewAllLink} style={{textDecoration: 'none'}}>
<Typography component="span" color="primary">
View All
</Typography>
<Link to={viewAllLink} style={{textDecoration: 'none'}}>
<Button
endIcon={<ArrowForwardIosIcon sx={{ fontSize: '0.875rem' }} />}
sx={{
color: 'primary.main',
'&:hover': {
backgroundColor: 'rgba(99, 102, 241, 0.1)',
},
}}
>
View All
</Button>
</Link>
</Box>
@@ -85,9 +93,12 @@ export function Carousel<T extends CarouselItem>({ title, items, renderItem, vie
transform: 'translateY(-50%)',
zIndex: 2,
backgroundColor: 'background.paper',
boxShadow: 2,
border: '1px solid rgba(99, 102, 241, 0.2)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
'&:hover': {
backgroundColor: 'action.hover',
backgroundColor: 'background.paper',
borderColor: 'rgba(99, 102, 241, 0.4)',
boxShadow: '0 8px 20px rgba(99, 102, 241, 0.3)',
},
visibility: scrollPosition <= 5 ? 'hidden' : 'visible',
}}
@@ -106,7 +117,7 @@ export function Carousel<T extends CarouselItem>({ title, items, renderItem, vie
'&::-webkit-scrollbar': {
display: 'none',
},
gap: '16px', // Fixed 16px gap - using string with px unit to ensure it's absolute
gap: '20px',
padding: '8px 4px',
}}
>
@@ -116,7 +127,7 @@ export function Carousel<T extends CarouselItem>({ title, items, renderItem, vie
sx={{
flex: '0 0 auto',
width: {
xs: '260px', // Fixed width at different breakpoints
xs: '260px',
sm: '280px',
md: '300px'
}
@@ -135,9 +146,12 @@ export function Carousel<T extends CarouselItem>({ title, items, renderItem, vie
transform: 'translateY(-50%)',
zIndex: 2,
backgroundColor: 'background.paper',
boxShadow: 2,
border: '1px solid rgba(99, 102, 241, 0.2)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
'&:hover': {
backgroundColor: 'action.hover',
backgroundColor: 'background.paper',
borderColor: 'rgba(99, 102, 241, 0.4)',
boxShadow: '0 8px 20px rgba(99, 102, 241, 0.3)',
},
visibility: scrollPosition >= maxScroll - 5 ? 'hidden' : 'visible',
}}

View File

@@ -1,13 +1,14 @@
import React from 'react';
import {
Box,
Avatar,
Typography,
Tooltip
Tooltip,
Skeleton
} from "@mui/material";
import PersonIcon from '@mui/icons-material/Person';
import { formatDistanceToNow, format } from "date-fns";
import { AuditEvent, decodeAuditEvent as auditEventMessage } from "@/app/ts/AuditEvent";
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
interface AuditEventItemProps {
event: AuditEvent;
@@ -15,17 +16,39 @@ interface AuditEventItemProps {
}
export default function AuditEventItem({ event, validatorUser }: AuditEventItemProps) {
const isValidator = event.User === validatorUser;
const { thumbnailUrl, isLoading } = useUserThumbnail(isValidator ? undefined : event.User, '150x150');
return (
<Box sx={{ display: 'flex', gap: 2 }}>
<Avatar
src={event.User === validatorUser ? undefined : `/thumbnails/user/${event.User}`}
>
<PersonIcon />
</Avatar>
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
<Skeleton
variant="circular"
sx={{
position: 'absolute',
width: 40,
height: 40,
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
<Avatar
src={isValidator ? undefined : (thumbnailUrl || undefined)}
sx={{
width: 40,
height: 40,
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
>
<PersonIcon />
</Avatar>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="subtitle2">
{event.User === validatorUser ? "Validator" : event.Username || "Unknown"}
{isValidator ? "Validator" : event.Username || "Unknown"}
</Typography>
<DateDisplay date={event.Date} />
</Box>

View File

@@ -1,4 +1,3 @@
import React from 'react';
import {
Box,
Stack,
@@ -22,18 +21,21 @@ export default function AuditEventsTabPanel({
);
return (
<Box role="tabpanel" hidden={activeTab !== 1}>
{activeTab === 1 && (
<Stack spacing={2}>
{filteredEvents.map((event, index) => (
<AuditEventItem
key={index}
event={event}
validatorUser={validatorUser}
/>
))}
</Stack>
)}
<Box
role="tabpanel"
sx={{
display: activeTab === 1 ? 'block' : 'none'
}}
>
<Stack spacing={2}>
{filteredEvents.map((event, index) => (
<AuditEventItem
key={index}
event={event}
validatorUser={validatorUser}
/>
))}
</Stack>
</Box>
);
}

View File

@@ -1,13 +1,14 @@
import React from 'react';
import {
Box,
Avatar,
Typography,
Tooltip
Tooltip,
Skeleton
} from "@mui/material";
import PersonIcon from '@mui/icons-material/Person';
import { formatDistanceToNow, format } from "date-fns";
import { AuditEvent, decodeAuditEvent } from "@/app/ts/AuditEvent";
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
interface CommentItemProps {
event: AuditEvent;
@@ -15,17 +16,39 @@ interface CommentItemProps {
}
export default function CommentItem({ event, validatorUser }: CommentItemProps) {
const isValidator = event.User === validatorUser;
const { thumbnailUrl, isLoading } = useUserThumbnail(isValidator ? undefined : event.User, '150x150');
return (
<Box sx={{ display: 'flex', gap: 2 }}>
<Avatar
src={event.User === validatorUser ? undefined : `/thumbnails/user/${event.User}`}
>
<PersonIcon />
</Avatar>
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
<Skeleton
variant="circular"
sx={{
position: 'absolute',
width: 40,
height: 40,
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
<Avatar
src={isValidator ? undefined : (thumbnailUrl || undefined)}
sx={{
width: 40,
height: 40,
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
>
<PersonIcon />
</Avatar>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="subtitle2">
{event.User === validatorUser ? "Validator" : event.Username || "Unknown"}
{isValidator ? "Validator" : event.Username || "Unknown"}
</Typography>
<DateDisplay date={event.Date} />
</Box>

View File

@@ -28,7 +28,7 @@ export default function CommentsAndAuditSection({
}: CommentsAndAuditSectionProps) {
const [activeTab, setActiveTab] = useState(0);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};

View File

@@ -1,14 +1,15 @@
import React from 'react';
import {
Box,
Stack,
Avatar,
TextField,
IconButton
IconButton,
Skeleton
} from "@mui/material";
import SendIcon from '@mui/icons-material/Send';
import { AuditEvent, AuditEventType } from "@/app/ts/AuditEvent";
import CommentItem from './CommentItem';
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
interface CommentsTabPanelProps {
activeTab: number;
@@ -34,34 +35,35 @@ export default function CommentsTabPanel({
);
return (
<Box role="tabpanel" hidden={activeTab !== 0}>
{activeTab === 0 && (
<>
<Stack spacing={2} sx={{ mb: 3 }}>
{commentEvents.length > 0 ? (
commentEvents.map((event, index) => (
<CommentItem
key={index}
event={event}
validatorUser={validatorUser}
/>
))
) : (
<Box sx={{ textAlign: 'center', py: 2, color: 'text.secondary' }}>
No Comments
</Box>
)}
</Stack>
{userId !== null && (
<CommentInput
newComment={newComment}
setNewComment={setNewComment}
handleCommentSubmit={handleCommentSubmit}
userId={userId}
<Box
role="tabpanel"
sx={{
display: activeTab === 0 ? 'block' : 'none'
}}
>
<Stack spacing={2} sx={{ mb: 3 }}>
{commentEvents.length > 0 ? (
commentEvents.map((event, index) => (
<CommentItem
key={index}
event={event}
validatorUser={validatorUser}
/>
)}
</>
))
) : (
<Box sx={{ textAlign: 'center', py: 2, color: 'text.secondary' }}>
No Comments
</Box>
)}
</Stack>
{userId !== null && (
<CommentInput
newComment={newComment}
setNewComment={setNewComment}
handleCommentSubmit={handleCommentSubmit}
userId={userId}
/>
)}
</Box>
);
@@ -75,11 +77,32 @@ interface CommentInputProps {
}
function CommentInput({ newComment, setNewComment, handleCommentSubmit, userId }: CommentInputProps) {
const { thumbnailUrl, isLoading } = useUserThumbnail(userId || undefined, '150x150');
return (
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-start' }}>
<Avatar
src={`/thumbnails/user/${userId}`}
/>
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
<Skeleton
variant="circular"
sx={{
position: 'absolute',
width: 40,
height: 40,
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
<Avatar
src={thumbnailUrl || undefined}
sx={{
width: 40,
height: 40,
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
</Box>
<TextField
fullWidth
multiline

View File

@@ -1,9 +1,6 @@
"use client"
import Link from "next/link"
import Image from "next/image";
import { UserInfo } from "@/app/ts/User";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom"
import { useState, useRef } from "react";
import { useUser } from "@/app/hooks/useUser";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
@@ -37,7 +34,7 @@ const navItems: HeaderButton[] = [
function HeaderButton(header: HeaderButton) {
return (
<Button color="inherit" component={Link} href={header.href}>
<Button color="inherit" component={Link} to={header.href}>
{header.name}
</Button>
);
@@ -47,14 +44,26 @@ export default function Header() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [mobileOpen, setMobileOpen] = useState(false);
const hasAnimated = useRef(false);
const handleLoginClick = () => {
window.location.href =
"/auth/oauth2/login?redirect=" + window.location.href;
const getAuthUrl = () => {
const hostname = window.location.hostname;
// Production only
if (hostname === 'maps.strafes.net') {
return 'https://auth.strafes.net';
}
// Default to staging (works for staging.strafes.net and localhost)
return 'https://auth.staging.strafes.net';
};
const [valid, setValid] = useState<boolean>(false);
const [user, setUser] = useState<UserInfo | null>(null);
const handleLoginClick = () => {
const authUrl = getAuthUrl();
window.location.href = `${authUrl}/oauth2/login?redirect=${window.location.href}`;
};
const { user, isLoggedIn } = useUser();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [quickLinksAnchor, setQuickLinksAnchor] = useState<null | HTMLElement>(null);
@@ -77,60 +86,34 @@ export default function Header() {
setQuickLinksAnchor(null);
};
useEffect(() => {
async function getLoginInfo() {
try {
const response = await fetch("/api/session/user");
if (!response.ok) {
setValid(false);
setUser(null);
return;
}
const userData = await response.json();
const isLoggedIn = userData && 'UserID' in userData;
setValid(isLoggedIn);
setUser(isLoggedIn ? userData : null);
} catch (error) {
console.error("Error fetching user data:", error);
setValid(false);
setUser(null);
}
}
getLoginInfo();
}, []);
// Mobile navigation drawer content
const drawer = (
<Box onClick={handleDrawerToggle} sx={{ textAlign: 'center' }}>
<List>
{navItems.map((item) => (
<ListItem key={item.name} disablePadding>
<ListItemButton component={Link} href={item.href} sx={{ textAlign: 'center' }}>
<ListItemButton component={Link} to={item.href} sx={{ textAlign: 'center' }}>
<ListItemText primary={item.name} />
</ListItemButton>
</ListItem>
))}
{valid && user && (
{isLoggedIn && user && (
<ListItem disablePadding>
<ListItemButton component={Link} href="/submit" sx={{ textAlign: 'center' }}>
<ListItemButton component={Link} to="/submit" sx={{ textAlign: 'center' }}>
<ListItemText primary="Submit Map" sx={{ color: 'success.main' }} />
</ListItemButton>
</ListItem>
)}
{!valid && (
{!isLoggedIn && (
<ListItem disablePadding>
<ListItemButton onClick={handleLoginClick} sx={{ textAlign: 'center' }}>
<ListItemText primary="Login" />
</ListItemButton>
</ListItem>
)}
{valid && user && (
{isLoggedIn && user && (
<ListItem disablePadding>
<ListItemButton component={Link} href="/auth" sx={{ textAlign: 'center' }}>
<ListItemButton component="a" href={getAuthUrl()} sx={{ textAlign: 'center' }}>
<ListItemText primary="Manage Account" />
</ListItemButton>
</ListItem>
@@ -150,7 +133,7 @@ export default function Header() {
return (
<AppBar position="static">
<Toolbar>
<Toolbar sx={{ py: 1 }}>
{isMobile && (
<IconButton
color="inherit"
@@ -165,20 +148,144 @@ export default function Header() {
{/* Desktop navigation */}
{!isMobile && (
<Box display="flex" flexGrow={1} gap={2} alignItems="center">
<Box display="flex" flexGrow={1} gap={1} alignItems="center">
{/* Logo/Brand */}
<Box
component={Link}
to="/"
sx={{
mr: 4,
textDecoration: 'none',
display: 'flex',
alignItems: 'center',
position: 'relative',
overflow: 'hidden',
'@keyframes speedLine': {
'0%': {
transform: 'translateX(-50px) scaleX(0.5)',
opacity: 0,
},
'40%': {
opacity: 0.8,
transform: 'translateX(0px) scaleX(1)',
},
'100%': {
opacity: 0,
transform: 'translateX(30px) scaleX(0.7)',
},
},
'@keyframes logoReveal': {
'0%': {
opacity: 0,
transform: 'translateX(-10px)',
filter: 'blur(2px)',
},
'100%': {
opacity: 1,
transform: 'translateX(0px)',
filter: 'blur(0px)',
},
},
'&::before, &::after': {
content: '""',
position: 'absolute',
left: 0,
width: '100%',
height: '2px',
background: 'linear-gradient(90deg, transparent 10%, rgba(59, 130, 246, 0.8) 50%, transparent 90%)',
pointerEvents: 'none',
animation: !hasAnimated.current ? 'speedLine 0.6s ease-out forwards' : 'none',
opacity: !hasAnimated.current ? 0 : undefined,
},
'&::before': {
top: '35%',
animationDelay: !hasAnimated.current ? '0s' : undefined,
},
'&::after': {
top: '65%',
animationDelay: !hasAnimated.current ? '0.08s' : undefined,
},
}}
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: 0,
width: '100%',
height: '1px',
background: 'linear-gradient(90deg, transparent 10%, rgba(139, 92, 246, 0.6) 50%, transparent 90%)',
animation: !hasAnimated.current ? 'speedLine 0.6s ease-out forwards' : 'none',
animationDelay: !hasAnimated.current ? '0.04s' : '0s',
opacity: !hasAnimated.current ? 0 : undefined,
pointerEvents: 'none',
}}
/>
<Typography
variant="h6"
sx={{
color: 'text.primary',
fontWeight: 700,
letterSpacing: '-0.01em',
fontSize: '1.125rem',
position: 'relative',
zIndex: 1,
opacity: !hasAnimated.current ? 0 : 1,
animation: !hasAnimated.current ? 'logoReveal 0.5s ease-out forwards' : 'none',
animationDelay: !hasAnimated.current ? '0.5s' : '0s',
}}
onAnimationEnd={() => {
hasAnimated.current = true;
}}
>
StrafesNET
</Typography>
</Box>
{navItems.map((item) => (
<HeaderButton key={item.name} name={item.name} href={item.href} />
<Button
key={item.name}
color="inherit"
component={Link}
to={item.href}
sx={{
px: 2,
py: 1,
borderRadius: 1.5,
fontSize: '0.9rem',
fontWeight: 500,
color: 'text.secondary',
transition: 'all 0.2s',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.08)',
color: 'text.primary',
},
}}
>
{item.name}
</Button>
))}
<Box sx={{ flexGrow: 1 }} /> {/* Push quick links to the right */}
<Box sx={{ flexGrow: 1 }} />
{/* Quick Links Dropdown */}
<Box>
<Button
color="inherit"
endIcon={<ArrowDropDownIcon />}
onClick={handleQuickLinksOpen}
sx={{ textTransform: 'none', fontSize: '0.95rem', px: 1 }}
sx={{
px: 2,
mr: 1,
borderRadius: 1.5,
fontSize: '0.9rem',
fontWeight: 500,
color: 'text.secondary',
transition: 'all 0.2s',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.08)',
color: 'text.primary',
},
}}
>
QUICK LINKS
Quick Links
</Button>
<Menu
anchorEl={quickLinksAnchor}
@@ -186,12 +293,20 @@ export default function Header() {
onClose={handleQuickLinksClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
sx={{
'& .MuiMenu-paper': {
mt: 1.5,
},
}}
>
{quickLinks.map(link => (
<MenuItem
key={link.name}
onClick={handleQuickLinksClose}
sx={{ minWidth: 180 }}
sx={{
minWidth: 200,
fontSize: '0.9rem',
}}
component="a"
href={link.href}
target="_blank"
@@ -209,30 +324,53 @@ export default function Header() {
{isMobile && <Box sx={{ flexGrow: 1 }} />}
{/* Right side of nav */}
<Box display="flex" gap={2}>
{!isMobile && valid && user && (
<Button variant="outlined" color="success" component={Link} href="/submit">
<Box display="flex" gap={2} alignItems="center">
{!isMobile && isLoggedIn && user && (
<Button
variant="contained"
color="primary"
component={Link}
to="/submit"
sx={{
px: 3,
}}
>
Submit Map
</Button>
)}
{!isMobile && valid && user ? (
{!isMobile && isLoggedIn && user ? (
<Box display="flex" alignItems="center">
<Button
onClick={handleMenuOpen}
color="inherit"
size="small"
style={{ textTransform: "none" }}
sx={{
textTransform: "none",
borderRadius: 1.5,
px: 1.5,
py: 0.75,
border: '1px solid rgba(255, 255, 255, 0.08)',
transition: 'all 0.2s',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.08)',
borderColor: 'rgba(59, 130, 246, 0.3)',
},
}}
>
<Image
<img
className="avatar"
width={28}
height={28}
priority={true}
src={user.AvatarURL}
alt={user.Username}
style={{ marginRight: 8 }}
style={{
marginRight: 8,
borderRadius: '50%',
}}
/>
<Typography variant="body1">{user.Username}</Typography>
<Typography variant="body2" sx={{ fontSize: '0.875rem', fontWeight: 500 }}>
{user.Username}
</Typography>
</Button>
<Menu
anchorEl={anchorEl}
@@ -240,38 +378,58 @@ export default function Header() {
onClose={handleMenuClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
horizontal: "right",
}}
sx={{
'& .MuiMenu-paper': {
mt: 1.5,
},
}}
>
<MenuItem component={Link} href="/auth">
Manage
<MenuItem
component="a"
href={getAuthUrl()}
sx={{
fontSize: '0.9rem',
}}
>
Manage Account
</MenuItem>
</Menu>
</Box>
) : !isMobile && (
<Button color="inherit" onClick={handleLoginClick}>
<Button
variant="outlined"
color="primary"
onClick={handleLoginClick}
sx={{
px: 3,
}}
>
Login
</Button>
)}
{/* In mobile view, display just the avatar if logged in */}
{isMobile && valid && user && (
{isMobile && isLoggedIn && user && (
<IconButton
onClick={handleMenuOpen}
color="inherit"
size="small"
>
<Image
<img
className="avatar"
width={28}
height={28}
priority={true}
width={32}
height={32}
src={user.AvatarURL}
alt={user.Username}
style={{
borderRadius: '50%',
}}
/>
</IconButton>
)}
@@ -284,10 +442,13 @@ export default function Header() {
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile
keepMounted: true,
}}
sx={{
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: 240 },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: 240,
},
}}
>
{drawer}

View File

@@ -1,7 +1,8 @@
import React from "react";
import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Divider, Grid, Typography} from "@mui/material";
import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Divider, Typography, Skeleton} from "@mui/material";
import {Explore, Person2} from "@mui/icons-material";
import {StatusChip} from "@/app/_components/statusChip";
import {Link} from "react-router-dom";
import {useAssetThumbnail, useUserThumbnail} from "@/app/hooks/useThumbnails";
interface MapCardProps {
displayName: string;
@@ -16,37 +17,42 @@ interface MapCardProps {
type: 'mapfix' | 'submission';
}
const CARD_WIDTH = 270;
export function MapCard(props: MapCardProps) {
const { thumbnailUrl: assetThumbnail, isLoading: assetLoading } = useAssetThumbnail(props.assetId);
const { thumbnailUrl: userThumbnail, isLoading: userLoading } = useUserThumbnail(props.authorId);
return (
<Grid size={{ xs: 12, sm: 6, md: 3 }} key={props.assetId}>
<Box sx={{
width: CARD_WIDTH,
mx: 'auto', // Center the card in its grid cell
}}>
<Card sx={{
width: CARD_WIDTH,
height: 340, // Fixed height for all cards
display: 'flex',
flexDirection: 'column',
}}>
<CardActionArea
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch'
}}
href={`/${props.type === 'submission' ? 'submissions' : 'mapfixes'}/${props.id}`}>
<Box sx={{ position: 'relative' }}>
<Card sx={{ height: '100%' }}>
<CardActionArea
component={Link}
to={`/${props.type === 'submission' ? 'submissions' : 'mapfixes'}/${props.id}`}>
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
<Skeleton
variant="rectangular"
height={180}
animation="wave"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: assetLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
/>
<CardMedia
component="img"
image={`/thumbnails/asset/${props.assetId}`}
image={assetThumbnail || '/placeholder-map.png'}
alt={props.displayName}
sx={{
height: 160, // Fixed height for all images
height: 180,
objectFit: 'cover',
opacity: assetLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out, transform 0.3s',
'&:hover': {
transform: 'scale(1.05)',
},
}}
/>
<Box
@@ -54,133 +60,103 @@ export function MapCard(props: MapCardProps) {
position: 'absolute',
top: 12,
right: 12,
opacity: assetLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out 0.1s',
}}
>
<StatusChip status={props.statusID}/>
</Box>
</Box>
<CardContent sx={{
flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
p: 2,
width: '100%',
}}>
<CardContent>
<Box>
<Typography
variant="subtitle1"
variant="h6"
component="div"
sx={{
mb: 1,
mb: 1.5,
fontWeight: 600,
color: '#fff',
lineHeight: '1.3',
// Allow text to wrap
lineHeight: '1.4',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
minHeight: '2.8em',
}}
>
{props.displayName}
</Typography>
<Box sx={{
display: 'flex',
mb: 1.5,
gap: 2,
mb: 2,
flexWrap: 'wrap',
}}>
<Explore sx={{
mr: 0.75,
mt: 0.25,
color: 'text.secondary',
fontSize: '0.9rem',
flexShrink: 0,
}} />
<Typography
variant="body2"
color="text.secondary"
sx={{
fontWeight: 500,
// Allow text to wrap
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
lineHeight: '1.2',
wordBreak: 'break-word',
}}
>
{props.gameID === 1 ? 'Bhop' : props.gameID === 2 ? 'Surf' : props.gameID === 5 ? 'Fly Trials' : props.gameID === 4 ? 'Deathrun' : 'Unknown'}
</Typography>
</Box>
<Box sx={{
display: 'flex',
mb: 1.5,
}}>
<Person2 sx={{
mr: 0.75,
mt: 0.25,
color: 'text.secondary',
fontSize: '0.9rem',
flexShrink: 0,
}} />
<Typography
variant="body2"
color="text.secondary"
sx={{
fontWeight: 500,
// Allow text to wrap
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
lineHeight: '1.2',
wordBreak: 'break-word',
}}
>
{props.author}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Explore sx={{ fontSize: '1rem', color: '#6366f1' }} />
<Typography variant="body2" color="text.secondary" fontSize="0.875rem">
{props.gameID === 1 ? 'Bhop' : props.gameID === 2 ? 'Surf' : props.gameID === 5 ? 'Fly Trials' : props.gameID === 4 ? 'Deathrun' : 'Unknown'}
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Person2 sx={{ fontSize: '1rem', color: '#8b5cf6' }} />
<Typography
variant="body2"
color="text.secondary"
fontSize="0.875rem"
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{props.author}
</Typography>
</Box>
</Box>
</Box>
<Box>
<Divider sx={{ my: 1.5 }} />
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Skeleton
variant="circular"
width={28}
height={28}
sx={{
position: 'absolute',
opacity: userLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
/>
<Avatar
src={`/thumbnails/user/${props.authorId}`}
src={userThumbnail || undefined}
alt={props.author}
sx={{
width: 24,
height: 24,
border: '1px solid rgba(255, 255, 255, 0.1)',
width: 28,
height: 28,
opacity: userLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
<Typography
variant="caption"
color="text.secondary"
sx={{
ml: 1,
color: 'text.secondary',
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{/*In the future author should be the username of the submitter not the info from the map*/}
{props.author} - {new Date(props.created * 1000).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
{new Date(props.created * 1000).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</Typography>
</Box>
</Box>
</CardContent>
</CardActionArea>
</Card>
</Box>
</Grid>
</CardActionArea>
</Card>
)
}

View File

@@ -1,43 +1,44 @@
import {Typography, Box, Avatar, keyframes} from "@mui/material";
import {Typography, Box, Avatar, keyframes, Skeleton} from "@mui/material";
import { StatusChip } from "@/app/_components/statusChip";
import { SubmissionStatus } from "@/app/ts/Submission";
import { MapfixStatus } from "@/app/ts/Mapfix";
import {Status, StatusMatches} from "@/app/ts/Status";
import { useState, useEffect } from "react";
import Link from "next/link";
import { Link } from "react-router-dom";
import LaunchIcon from '@mui/icons-material/Launch';
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
import { useUsername } from "@/app/hooks/useUsername";
function SubmitterName({ submitterId }: { submitterId: number }) {
const [name, setName] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const { username, isLoading } = useUsername(submitterId);
useEffect(() => {
if (!submitterId) return;
const fetchUserName = async () => {
try {
setLoading(true);
const response = await fetch(`/proxy/users/${submitterId}`);
if (!response.ok) throw new Error('Failed to fetch user');
const data = await response.json();
setName(`@${data.name}`);
} catch {
setName(String(submitterId));
} finally {
setLoading(false);
}
};
fetchUserName();
}, [submitterId]);
const displayName = username ? `@${username}` : String(submitterId);
if (loading) return <Typography variant="body1">Loading...</Typography>;
return <Link href={`https://www.roblox.com/users/${submitterId}/profile`} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, '&:hover': { textDecoration: 'underline' } }}>
<Typography>
{name || submitterId}
</Typography>
<LaunchIcon sx={{ fontSize: '1rem', color: 'text.secondary' }} />
return <a href={`https://www.roblox.com/users/${submitterId}/profile`} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, '&:hover': { textDecoration: 'underline' }, position: 'relative' }}>
<Skeleton
variant="text"
width={80}
sx={{
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
position: isLoading ? 'relative' : 'absolute',
}}
animation="wave"
/>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 0.5,
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}>
<Typography>
{displayName}
</Typography>
<LaunchIcon sx={{ fontSize: '1rem', color: 'text.secondary' }} />
</Box>
</Box>
</Link>
</a>
}
interface ReviewItemHeaderProps {
@@ -50,6 +51,7 @@ interface ReviewItemHeaderProps {
export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, submitterId }: ReviewItemHeaderProps) => {
const isProcessing = StatusMatches(statusId, [Status.Validating, Status.Uploading, Status.Submitting]);
const { thumbnailUrl, isLoading } = useUserThumbnail(submitterId, '150x150');
const pulse = keyframes`
0%, 100% { opacity: 0.2; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1); }
@@ -59,7 +61,7 @@ export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, subm
<>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
{assetId != null ? (
<Link href={`/maps/${assetId}`} passHref legacyBehavior>
<Link to={`/maps/${assetId}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Box sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} title="View related map">
<Typography
variant="h4"
@@ -111,10 +113,28 @@ export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, subm
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<Avatar
src={`/thumbnails/user/${submitterId}`}
sx={{ mr: 1, width: 24, height: 24 }}
/>
<Box sx={{ position: 'relative', mr: 1, width: 24, height: 24 }}>
<Skeleton
variant="circular"
sx={{
position: 'absolute',
width: 24,
height: 24,
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
<Avatar
src={thumbnailUrl || undefined}
sx={{
width: 24,
height: 24,
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
</Box>
<SubmitterName submitterId={submitterId} />
</Box>
</>

View File

@@ -1,8 +1,8 @@
import React, {JSX} from "react";
import {JSX} from "react";
import {Cancel, CheckCircle, Pending} from "@mui/icons-material";
import {Chip} from "@mui/material";
export const StatusChip = ({status}: { status: number }) => {
export const StatusChip = ({status}: { status: number }): JSX.Element => {
let color: 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' = 'default';
let icon: JSX.Element = <Pending fontSize="small"/>;
let label: string = 'Unknown';
@@ -81,12 +81,6 @@ export const StatusChip = ({status}: { status: number }) => {
label={label}
color={color}
size="small"
sx={{
height: 24,
fontSize: '0.75rem',
fontWeight: 600,
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
}}
/>
);
};

View File

@@ -1,5 +1,3 @@
"use client"
import Header from "./header";
export default function Webpage({children}: Readonly<{children?: React.ReactNode}>) {

View File

@@ -1,4 +1,4 @@
@use "../../globals.scss";
@use "../globals.scss";
::placeholder {
color: var(--placeholder-text)
@@ -47,8 +47,4 @@ header h1 {
form {
display: grid;
gap: 25px;
fieldset {
border: blue
}
}

View File

@@ -1,5 +1,3 @@
"use client"
import { Button, TextField } from "@mui/material"
import GameSelection from "./_game";
@@ -8,7 +6,7 @@ import Webpage from "@/app/_components/webpage"
import React, { useState } from "react";
import {useTitle} from "@/app/hooks/useTitle";
import "./(styles)/page.scss"
import "./page.scss"
interface SubmissionPayload {
AssetID: number;
@@ -43,7 +41,7 @@ export default function SubmissionInfoPage() {
try {
// Send the POST request
const response = await fetch("/api/submissions-admin", {
const response = await fetch("/v1/submissions-admin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),

View File

@@ -40,11 +40,11 @@ export function useReviewData({itemType, itemId}: UseReviewDataProps): UseReview
try {
const [reviewData, auditData] = await Promise.all([
fetch(`/api/${itemType}/${itemId}`).then(res => {
fetch(`/v1/${itemType}/${itemId}`).then(res => {
if (!res.ok) throw new Error(`Failed to fetch ${itemType.slice(0, -1)}: ${res.status}`);
return res.json();
}),
fetch(`/api/${itemType}/${itemId}/audit-events?Page=1&Limit=100`).then(res => {
fetch(`/v1/${itemType}/${itemId}/audit-events?Page=1&Limit=100`).then(res => {
if (!res.ok) throw new Error(`Failed to fetch audit events: ${res.status}`);
return res.json();
})
@@ -58,7 +58,7 @@ export function useReviewData({itemType, itemId}: UseReviewDataProps): UseReview
}
try {
const rolesResponse = await fetch("/api/session/roles");
const rolesResponse = await fetch("/v1/session/roles");
if (rolesResponse.ok) {
const rolesData = await rolesResponse.json();
setRoles(rolesData.Roles);
@@ -72,7 +72,7 @@ export function useReviewData({itemType, itemId}: UseReviewDataProps): UseReview
}
try {
const userResponse = await fetch("/api/session/user");
const userResponse = await fetch("/v1/session/user");
if (userResponse.ok) {
const userData = await userResponse.json();
setUser(userData.UserID);

View File

@@ -0,0 +1,216 @@
import { useEffect, useState } from 'react';
type ThumbnailSize = '150x150' | '420x420' | '768x432';
interface ThumbnailBatchResponse {
thumbnails: Record<string, string>;
}
// Batching queue
class ThumbnailBatcher {
private assetQueue: Map<number, Set<(url: string | null) => void>> = new Map();
private userQueue: Map<number, Set<(url: string | null) => void>> = new Map();
private assetTimeoutId: NodeJS.Timeout | null = null;
private userTimeoutId: NodeJS.Timeout | null = null;
private batchDelay = 50; // 50ms delay to collect requests
async fetchAssetBatch(ids: number[], size: ThumbnailSize): Promise<Record<number, string>> {
try {
const response = await fetch('/v1/thumbnails/assets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ assetIds: ids, size }),
});
if (!response.ok) {
throw new Error(`Failed to fetch thumbnails: ${response.statusText}`);
}
const data: ThumbnailBatchResponse = await response.json();
// Convert string keys back to numbers
const result: Record<number, string> = {};
Object.entries(data.thumbnails || {}).forEach(([key, value]) => {
result[parseInt(key, 10)] = value;
});
return result;
} catch (error) {
console.error('Error fetching asset thumbnails:', error);
return {};
}
}
async fetchUserBatch(ids: number[], size: ThumbnailSize): Promise<Record<number, string>> {
try {
const response = await fetch('/v1/thumbnails/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds: ids, size }),
});
if (!response.ok) {
throw new Error(`Failed to fetch user thumbnails: ${response.statusText}`);
}
const data: ThumbnailBatchResponse = await response.json();
// Convert string keys back to numbers
const result: Record<number, string> = {};
Object.entries(data.thumbnails || {}).forEach(([key, value]) => {
result[parseInt(key, 10)] = value;
});
return result;
} catch (error) {
console.error('Error fetching user thumbnails:', error);
return {};
}
}
queueAssetRequest(id: number, size: ThumbnailSize, callback: (url: string | null) => void) {
if (!this.assetQueue.has(id)) {
this.assetQueue.set(id, new Set());
}
this.assetQueue.get(id)!.add(callback);
if (this.assetTimeoutId) {
clearTimeout(this.assetTimeoutId);
}
this.assetTimeoutId = setTimeout(() => {
this.flushAssetQueue(size);
}, this.batchDelay);
}
queueUserRequest(id: number, size: ThumbnailSize, callback: (url: string | null) => void) {
if (!this.userQueue.has(id)) {
this.userQueue.set(id, new Set());
}
this.userQueue.get(id)!.add(callback);
if (this.userTimeoutId) {
clearTimeout(this.userTimeoutId);
}
this.userTimeoutId = setTimeout(() => {
this.flushUserQueue(size);
}, this.batchDelay);
}
private async flushAssetQueue(size: ThumbnailSize) {
if (this.assetQueue.size === 0) return;
const ids = Array.from(this.assetQueue.keys());
const callbacks = new Map(this.assetQueue);
this.assetQueue.clear();
// Split into batches of 100 (API limit)
for (let i = 0; i < ids.length; i += 100) {
const batchIds = ids.slice(i, i + 100);
const results = await this.fetchAssetBatch(batchIds, size);
batchIds.forEach((id) => {
const url = results[id] || null;
const cbs = callbacks.get(id);
if (cbs) {
cbs.forEach((cb) => cb(url));
}
});
}
}
private async flushUserQueue(size: ThumbnailSize) {
if (this.userQueue.size === 0) return;
const ids = Array.from(this.userQueue.keys());
const callbacks = new Map(this.userQueue);
this.userQueue.clear();
// Split into batches of 100 (API limit)
for (let i = 0; i < ids.length; i += 100) {
const batchIds = ids.slice(i, i + 100);
const results = await this.fetchUserBatch(batchIds, size);
batchIds.forEach((id) => {
const url = results[id] || null;
const cbs = callbacks.get(id);
if (cbs) {
cbs.forEach((cb) => cb(url));
}
});
}
}
}
// Singleton batcher instance
const batcher = new ThumbnailBatcher();
/**
* Hook to fetch a single asset thumbnail with automatic batching
*/
export function useAssetThumbnail(assetId: number | undefined, size: ThumbnailSize = '420x420') {
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!assetId) {
setThumbnailUrl(null);
setIsLoading(false);
return;
}
setIsLoading(true);
batcher.queueAssetRequest(assetId, size, (url) => {
setThumbnailUrl(url);
setIsLoading(false);
});
}, [assetId, size]);
return { thumbnailUrl, isLoading };
}
/**
* Hook to fetch a single user avatar thumbnail with automatic batching
*/
export function useUserThumbnail(userId: number | undefined, size: ThumbnailSize = '150x150') {
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!userId) {
setThumbnailUrl(null);
setIsLoading(false);
return;
}
setIsLoading(true);
batcher.queueUserRequest(userId, size, (url) => {
setThumbnailUrl(url);
setIsLoading(false);
});
}, [userId, size]);
return { thumbnailUrl, isLoading };
}
/**
* Hook to prefetch multiple thumbnails (useful for lists)
* This ensures they're batched together
*/
export function usePrefetchThumbnails(
items: Array<{ assetId?: number; userId?: number }>,
assetSize: ThumbnailSize = '420x420',
userSize: ThumbnailSize = '150x150'
) {
useEffect(() => {
items.forEach((item) => {
if (item.assetId) {
batcher.queueAssetRequest(item.assetId, assetSize, () => {});
}
if (item.userId) {
batcher.queueUserRequest(item.userId, userSize, () => {});
}
});
}, [items, assetSize, userSize]);
}

View File

@@ -1,5 +1,3 @@
'use client';
import { useEffect } from 'react';
export function useTitle(title: string) {

View File

@@ -0,0 +1,39 @@
import { useQuery } from '@tanstack/react-query';
import { UserInfo } from '@/app/ts/User';
async function fetchUser(): Promise<UserInfo | null> {
try {
const response = await fetch('/v1/session/user');
if (!response.ok) {
return null;
}
const userData = await response.json();
if (userData && 'UserID' in userData) {
return userData as UserInfo;
}
return null;
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
export function useUser() {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
staleTime: Infinity, // User data won't go stale unless manually invalidated
gcTime: Infinity, // Keep in cache indefinitely
});
return {
user,
isLoggedIn: user !== null && user !== undefined,
isLoading,
error,
};
}

View File

@@ -0,0 +1,103 @@
import { useEffect, useState } from 'react';
interface UserBatchResponse {
usernames: Record<string, string>;
}
// Batching queue
class UserBatcher {
private queue: Map<number, Set<(name: string | null) => void>> = new Map();
private timeoutId: NodeJS.Timeout | null = null;
private batchDelay = 50; // 50ms delay to collect requests
async fetchBatch(ids: number[]): Promise<Record<number, string>> {
try {
const response = await fetch('/v1/usernames', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds: ids }),
});
if (!response.ok) {
throw new Error(`Failed to fetch usernames: ${response.statusText}`);
}
const data: UserBatchResponse = await response.json();
// Convert string keys back to numbers
const result: Record<number, string> = {};
Object.entries(data.usernames || {}).forEach(([key, value]) => {
result[parseInt(key, 10)] = value;
});
return result;
} catch (error) {
console.error('Error fetching usernames:', error);
return {};
}
}
queueRequest(id: number, callback: (name: string | null) => void) {
if (!this.queue.has(id)) {
this.queue.set(id, new Set());
}
this.queue.get(id)!.add(callback);
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
this.flushQueue();
}, this.batchDelay);
}
private async flushQueue() {
if (this.queue.size === 0) return;
const ids = Array.from(this.queue.keys());
const callbacks = new Map(this.queue);
this.queue.clear();
// Split into batches of 100 (API limit)
for (let i = 0; i < ids.length; i += 100) {
const batchIds = ids.slice(i, i + 100);
const results = await this.fetchBatch(batchIds);
batchIds.forEach((id) => {
const name = results[id] || null;
const cbs = callbacks.get(id);
if (cbs) {
cbs.forEach((cb) => cb(name));
}
});
}
}
}
// Singleton batcher instance
const batcher = new UserBatcher();
/**
* Hook to fetch a single username with automatic batching
*/
export function useUsername(userId: number | undefined) {
const [username, setUsername] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!userId) {
setUsername(null);
setIsLoading(false);
return;
}
setIsLoading(true);
batcher.queueRequest(userId, (name) => {
setUsername(name);
setIsLoading(false);
});
}, [userId]);
return { username, isLoading };
}

View File

@@ -1,17 +0,0 @@
"use client";
import "./globals.scss";
import {theme} from "@/app/lib/theme";
import {ThemeProvider} from "@mui/material";
export default function RootLayout({children}: Readonly<{children: React.ReactNode}>) {
return (
<html lang="en">
<body>
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,35 +0,0 @@
import path from 'path';
import { promises as fs } from 'fs';
import { NextResponse } from 'next/server';
export async function errorImageResponse(
statusCode: number = 500,
options?: { message?: string }
): Promise<NextResponse> {
const file = `${statusCode}.png`;
const filePath = path.join(process.cwd(), 'public/errors', file);
const headers: Record<string, string> = {
'Content-Type': 'image/png',
};
if (options?.message) {
headers['X-Error-Message'] = encodeURIComponent(options.message);
}
try {
const buffer = await fs.readFile(filePath);
headers['Content-Length'] = buffer.length.toString();
return new NextResponse(buffer, {
status: statusCode,
headers,
});
} catch {
const fallback = path.join(process.cwd(), 'public/errors', '500.png');
const buffer = await fs.readFile(fallback);
headers['Content-Length'] = buffer.length.toString();
return new NextResponse(buffer, {
status: 500,
headers,
});
}
}

View File

@@ -4,47 +4,106 @@ export const theme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#90caf9',
main: '#3b82f6',
dark: '#2563eb',
light: '#60a5fa',
},
secondary: {
main: '#f48fb1',
main: '#8b5cf6',
dark: '#7c3aed',
light: '#a78bfa',
},
background: {
default: '#121212',
paper: '#1e1e1e',
default: '#0a0a0a',
paper: '#171717',
},
text: {
primary: '#ffffff',
secondary: '#9ca3af',
},
error: {
main: '#ef4444',
light: '#f87171',
dark: '#dc2626',
},
warning: {
main: '#f59e0b',
light: '#fbbf24',
dark: '#d97706',
},
success: {
main: '#10b981',
light: '#34d399',
dark: '#059669',
},
info: {
main: '#3b82f6',
light: '#60a5fa',
dark: '#2563eb',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif',
h1: {
fontWeight: 700,
letterSpacing: '-0.025em',
},
h2: {
fontWeight: 700,
letterSpacing: '-0.02em',
},
h3: {
fontWeight: 600,
letterSpacing: '-0.015em',
},
h4: {
fontWeight: 600,
letterSpacing: '-0.01em',
},
h5: {
fontWeight: 500,
letterSpacing: '0.5px',
fontWeight: 600,
},
h6: {
fontWeight: 600,
},
subtitle1: {
fontWeight: 500,
fontSize: '0.95rem',
fontSize: '1rem',
},
body1: {
fontSize: '1rem',
lineHeight: 1.7,
},
body2: {
fontSize: '0.875rem',
lineHeight: 1.6,
},
caption: {
fontSize: '0.75rem',
},
button: {
fontWeight: 600,
textTransform: 'none',
letterSpacing: '0.01em',
},
},
shape: {
borderRadius: 8,
borderRadius: 12,
},
components: {
MuiCard: {
styleOverrides: {
root: {
borderRadius: 8,
borderRadius: 12,
overflow: 'hidden',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
backgroundColor: '#171717',
border: '1px solid rgba(255, 255, 255, 0.08)',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.2)',
border: '1px solid rgba(59, 130, 246, 0.4)',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(59, 130, 246, 0.2)',
},
},
},
@@ -52,7 +111,7 @@ export const theme = createTheme({
MuiCardMedia: {
styleOverrides: {
root: {
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
transition: 'transform 0.3s',
},
},
},
@@ -69,14 +128,48 @@ export const theme = createTheme({
MuiChip: {
styleOverrides: {
root: {
fontWeight: 500,
fontWeight: 600,
borderRadius: 6,
fontSize: '0.75rem',
transition: 'all 0.2s ease-in-out',
},
icon: {
marginLeft: '8px',
},
colorError: {
backgroundColor: '#ef4444',
color: '#ffffff',
'& .MuiChip-icon': {
color: '#ffffff',
},
},
colorWarning: {
backgroundColor: '#f59e0b',
color: '#ffffff',
'& .MuiChip-icon': {
color: '#ffffff',
},
},
colorSuccess: {
backgroundColor: '#10b981',
color: '#ffffff',
'& .MuiChip-icon': {
color: '#ffffff',
},
},
colorInfo: {
backgroundColor: '#3b82f6',
color: '#ffffff',
'& .MuiChip-icon': {
color: '#ffffff',
},
},
},
},
MuiDivider: {
styleOverrides: {
root: {
borderColor: 'rgba(255, 255, 255, 0.1)',
borderColor: 'rgba(148, 163, 184, 0.1)',
},
},
},
@@ -84,6 +177,126 @@ export const theme = createTheme({
styleOverrides: {
root: {
backgroundImage: 'none',
backgroundColor: '#171717',
},
},
},
MuiButton: {
styleOverrides: {
root: {
borderRadius: 8,
fontWeight: 600,
textTransform: 'none',
padding: '10px 24px',
transition: 'all 0.2s ease-in-out',
},
contained: {
boxShadow: 'none',
'&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
transform: 'translateY(-1px)',
},
},
containedPrimary: {
background: '#3b82f6',
'&:hover': {
background: '#2563eb',
},
},
outlined: {
borderWidth: '1.5px',
'&:hover': {
borderWidth: '1.5px',
backgroundColor: 'rgba(59, 130, 246, 0.08)',
},
},
outlinedPrimary: {
borderColor: 'rgba(59, 130, 246, 0.5)',
'&:hover': {
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.08)',
},
},
outlinedSecondary: {
borderColor: 'rgba(139, 92, 246, 0.5)',
'&:hover': {
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.08)',
},
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
background: 'rgba(10, 10, 10, 0.8)',
backdropFilter: 'blur(12px)',
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
boxShadow: 'none',
},
},
},
MuiDrawer: {
styleOverrides: {
paper: {
backgroundColor: '#0a0a0a',
borderRight: '1px solid rgba(255, 255, 255, 0.08)',
},
},
},
MuiCircularProgress: {
styleOverrides: {
root: {
color: '#3b82f6',
},
},
},
MuiIconButton: {
styleOverrides: {
root: {
transition: 'all 0.2s ease-in-out',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.1)',
},
},
},
},
MuiLink: {
styleOverrides: {
root: {
color: '#60a5fa',
textDecoration: 'none',
transition: 'color 0.2s ease-in-out',
'&:hover': {
color: '#3b82f6',
textDecoration: 'underline',
},
},
},
},
MuiMenu: {
styleOverrides: {
paper: {
background: '#171717',
backdropFilter: 'blur(12px)',
border: '1px solid rgba(255, 255, 255, 0.08)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.4)',
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
transition: 'all 0.2s ease-in-out',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.1)',
},
'&.Mui-selected': {
backgroundColor: 'rgba(59, 130, 246, 0.15)',
'&:hover': {
backgroundColor: 'rgba(59, 130, 246, 0.2)',
},
},
},
},
},

View File

@@ -1,3 +0,0 @@
export const thumbnailLoader = ({ src, width, quality }: { src: string, width: number, quality?: number }) => {
return `${src}?w=${width}&q=${quality || 75}`;
};

View File

@@ -1,9 +1,8 @@
"use client";
import Webpage from "@/app/_components/webpage";
import { useParams, useRouter } from "next/navigation";
import { useParams, useNavigate } from "react-router-dom";
import {useState} from "react";
import Link from "next/link";
import { Link } from "react-router-dom";
import { useAssetThumbnail } from "@/app/hooks/useThumbnails";
// MUI Components
import {
@@ -36,7 +35,7 @@ interface SnackbarState {
export default function MapfixDetailsPage() {
const { mapfixId } = useParams<{ mapfixId: string }>();
const router = useRouter();
const navigate = useNavigate();
const [newComment, setNewComment] = useState("");
const [showBeforeImage, setShowBeforeImage] = useState(false);
const [snackbar, setSnackbar] = useState<SnackbarState>({
@@ -70,16 +69,26 @@ export default function MapfixDetailsPage() {
refreshData
} = useReviewData({
itemType: 'mapfixes',
itemId: mapfixId
itemId: mapfixId!
});
const mapfix = mapfixData as MapfixInfo;
useTitle(mapfix ? `${mapfix.DisplayName} Mapfix` : 'Loading Mapfix...');
// Use thumbnail hooks for before/after images
const { thumbnailUrl: beforeThumbnail, isLoading: beforeLoading } = useAssetThumbnail(
mapfix?.TargetAssetID,
'420x420'
);
const { thumbnailUrl: afterThumbnail, isLoading: afterLoading } = useAssetThumbnail(
mapfix?.AssetID,
'420x420'
);
// Handle review button actions
async function handleReviewAction(action: string, mapfixId: number) {
try {
const response = await fetch(`/api/mapfixes/${mapfixId}/status/${action}`, {
const response = await fetch(`/v1/mapfixes/${mapfixId}/status/${action}`, {
method: "POST",
headers: {
"Content-type": "application/json",
@@ -118,7 +127,7 @@ export default function MapfixDetailsPage() {
}
try {
const response = await fetch(`/api/mapfixes/${mapfixId}/comment`, {
const response = await fetch(`/v1/mapfixes/${mapfixId}/comment`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
@@ -177,7 +186,7 @@ export default function MapfixDetailsPage() {
title="Error Loading Mapfix"
message={error || "Mapfix not found"}
buttonText="Return to Mapfixes"
onButtonClick={() => router.push('/mapfixes')}
onButtonClick={() => navigate('/mapfixes')}
/>
);
}
@@ -191,10 +200,10 @@ export default function MapfixDetailsPage() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Link href="/mapfixes" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/mapfixes" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Mapfixes</Typography>
</Link>
<Typography color="text.secondary">{mapfix.DisplayName}</Typography>
@@ -207,6 +216,22 @@ export default function MapfixDetailsPage() {
<Box sx={{ position: 'relative', width: '100%', aspectRatio: '1/1' }}>
{/* Before/After Images Container */}
<Box sx={{ position: 'relative', width: '100%', height: '100%' }}>
{/* Loading Skeleton for Before Image */}
<Skeleton
variant="rectangular"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1,
opacity: beforeLoading && showBeforeImage ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
{/* Before Image */}
<Box
sx={{
@@ -216,18 +241,34 @@ export default function MapfixDetailsPage() {
width: '100%',
height: '100%',
zIndex: 1,
opacity: showBeforeImage ? 1 : 0,
opacity: showBeforeImage ? (beforeLoading ? 0 : 1) : 0,
itzaname marked this conversation as resolved
Review

wtf double ternary

wtf double ternary
transition: 'opacity 0.5s ease-in-out'
}}
>
<CardMedia
component="img"
image={`/thumbnails/asset/${mapfix.TargetAssetID}`}
image={beforeThumbnail || '/placeholder-map.png'}
alt="Before Map Thumbnail"
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
{/* Loading Skeleton for After Image */}
<Skeleton
variant="rectangular"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
opacity: afterLoading && !showBeforeImage ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
{/* After Image */}
<Box
sx={{
@@ -237,13 +278,13 @@ export default function MapfixDetailsPage() {
width: '100%',
height: '100%',
zIndex: 0,
opacity: showBeforeImage ? 0 : 1,
opacity: showBeforeImage ? 0 : (afterLoading ? 0 : 1),
transition: 'opacity 0.5s ease-in-out'
}}
>
<CardMedia
component="img"
image={`/thumbnails/asset/${mapfix.AssetID}`}
image={afterThumbnail || '/placeholder-map.png'}
alt="After Map Thumbnail"
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>

View File

@@ -1,5 +1,3 @@
'use client'
import { useState, useEffect } from "react";
import { MapfixList } from "../ts/Mapfix";
import { MapCard } from "../_components/mapCard";
@@ -8,12 +6,14 @@ import { ListSortConstants } from "../ts/Sort";
import {
Box,
Breadcrumbs,
CircularProgress,
Card,
CardContent,
Container,
Pagination,
Skeleton,
Typography
} from "@mui/material";
import Link from "next/link";
import { Link } from "react-router-dom";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
import {useTitle} from "@/app/hooks/useTitle";
@@ -32,7 +32,7 @@ export default function MapfixInfoPage() {
setIsLoading(true);
try {
const res = await fetch(
`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`,
`/v1/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`,
{ signal: controller.signal }
);
@@ -55,33 +55,37 @@ export default function MapfixInfoPage() {
return () => controller.abort();
}, [currentPage]);
if (isLoading || !mapfixes) {
const skeletonCards = Array.from({ length: cardsPerPage }, (_, i) => i);
const totalPages = mapfixes ? Math.ceil(mapfixes.Total / cardsPerPage) : 0;
if (mapfixes && mapfixes.Total === 0) {
return (
<Webpage>
<Container sx={{ height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Box display="flex" flexDirection="column" alignItems="center">
<CircularProgress />
<Typography variant="body1" sx={{ mt: 2 }}>
Loading mapfixes...
</Typography>
</Box>
<Container sx={{ py: 6 }}>
<Typography variant="body1">
Mapfixes list is empty.
</Typography>
</Container>
</Webpage>
);
}
const totalPages = Math.ceil(mapfixes.Total / cardsPerPage);
return (
<Webpage>
<Container maxWidth="lg" sx={{ py: 6 }}>
<Box component="main" sx={{ width: '100%', px: 2 }}>
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
py: 6,
px: 2
}}>
<Box sx={{ width: '100%', maxWidth: '1200px' }}>
<Breadcrumbs
separator={<NavigateNextIcon fontSize="small" />}
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Typography color="text.secondary">Mapfixes</Typography>
@@ -99,26 +103,51 @@ export default function MapfixInfoPage() {
className="grid"
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
gridTemplateColumns: {
xs: 'repeat(1, 1fr)',
sm: 'repeat(2, 1fr)',
md: 'repeat(3, 1fr)',
lg: 'repeat(4, 1fr)',
},
gap: 3,
width: '100%',
}}
>
{mapfixes.Mapfixes.map((mapfix) => (
<MapCard
key={mapfix.ID}
id={mapfix.ID}
assetId={mapfix.AssetID}
displayName={mapfix.DisplayName}
author={mapfix.Creator}
authorId={mapfix.Submitter}
rating={mapfix.StatusID}
statusID={mapfix.StatusID}
gameID={mapfix.GameID}
created={mapfix.CreatedAt}
type="mapfix"
/>
))}
{!mapfixes || isLoading ? (
skeletonCards.map((i) => (
<Card key={i} sx={{ height: '100%' }}>
<Skeleton variant="rectangular" height={180} />
<CardContent>
<Skeleton variant="text" width="80%" height={32} sx={{ mb: 1.5 }} />
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
<Skeleton variant="text" width={80} />
<Skeleton variant="text" width={100} />
</Box>
<Skeleton variant="text" width="60%" />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 2 }}>
<Skeleton variant="circular" width={28} height={28} />
<Skeleton variant="text" width={100} />
</Box>
</CardContent>
</Card>
))
) : (
mapfixes.Mapfixes.map((mapfix) => (
<MapCard
key={mapfix.ID}
id={mapfix.ID}
assetId={mapfix.AssetID}
displayName={mapfix.DisplayName}
author={mapfix.Creator}
authorId={mapfix.Submitter}
rating={mapfix.StatusID}
statusID={mapfix.StatusID}
gameID={mapfix.GameID}
created={mapfix.CreatedAt}
type="mapfix"
/>
))
)}
</Box>
{totalPages > 1 && (
@@ -133,7 +162,7 @@ export default function MapfixInfoPage() {
</Box>
)}
</Box>
</Container>
</Box>
</Webpage>
);
}

View File

@@ -1,5 +1,3 @@
"use client"
import React, { useState, useEffect } from "react";
import {
Button,
@@ -16,8 +14,7 @@ import {
import SendIcon from '@mui/icons-material/Send';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import Webpage from "@/app/_components/webpage";
import { useParams } from "next/navigation";
import Link from "next/link";
import { useParams, Link, useNavigate } from "react-router-dom";
import {MapInfo} from "@/app/ts/Map";
import {useTitle} from "@/app/hooks/useTitle";
@@ -36,6 +33,7 @@ const gameTypes: Record<number, string> = {
export default function MapfixInfoPage() {
const { mapId } = useParams<{ mapId: string }>();
const navigate = useNavigate();
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [mapDetails, setMapDetails] = useState<MapInfo | null>(null);
@@ -48,7 +46,7 @@ export default function MapfixInfoPage() {
const fetchMapDetails = async () => {
try {
setIsLoading(true);
const response = await fetch(`/api/maps/${mapId}`);
const response = await fetch(`/v1/maps/${mapId}`);
if (!response.ok) {
throw new Error(`Failed to fetch map details: ${response.statusText}`);
@@ -106,7 +104,7 @@ export default function MapfixInfoPage() {
};
try {
const response = await fetch("/api/mapfixes", {
const response = await fetch("/v1/mapfixes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
@@ -118,7 +116,7 @@ export default function MapfixInfoPage() {
}
const { OperationID } = await response.json();
window.location.assign(`/operations/${OperationID}`);
navigate(`/operations/${OperationID}`);
} catch (error) {
console.error("Error submitting data:", error);
setError(error instanceof Error ? error.message : "An unknown error occurred");
@@ -134,14 +132,14 @@ export default function MapfixInfoPage() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Link href="/maps" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/maps" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Maps</Typography>
</Link>
{mapDetails && (
<Link href={`/maps/${mapId}`} passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to={`/maps/${mapId}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">{mapDetails.DisplayName}</Typography>
</Link>
)}

View File

@@ -1,13 +1,12 @@
"use client";
import { MapInfo } from "@/app/ts/Map";
import Webpage from "@/app/_components/webpage";
import { useParams, useRouter } from "next/navigation";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useParams, useNavigate } from "react-router-dom";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Snackbar, Alert } from "@mui/material";
import { MapfixStatus, type MapfixInfo } from "@/app/ts/Mapfix";
import LaunchIcon from '@mui/icons-material/Launch';
import { useAssetThumbnail } from "@/app/hooks/useThumbnails";
// MUI Components
import {
@@ -39,7 +38,7 @@ import {useTitle} from "@/app/hooks/useTitle";
export default function MapDetails() {
const { mapId } = useParams();
const router = useRouter();
const navigate = useNavigate();
const [map, setMap] = useState<MapInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -49,12 +48,18 @@ export default function MapDetails() {
useTitle(map ? `${map.DisplayName}` : 'Loading Map...');
// Use thumbnail hook for the map preview image
const { thumbnailUrl, isLoading: thumbnailLoading } = useAssetThumbnail(
map?.ID,
'768x432'
);
useEffect(() => {
async function getMap() {
try {
setLoading(true);
setError(null);
const res = await fetch(`/api/maps/${mapId}`);
const res = await fetch(`/v1/maps/${mapId}`);
if (!res.ok) {
throw new Error(`Failed to fetch map: ${res.status}`);
}
@@ -73,7 +78,7 @@ export default function MapDetails() {
useEffect(() => {
async function getRoles() {
try {
const rolesResponse = await fetch("/api/session/roles");
const rolesResponse = await fetch("/v1/session/roles");
if (rolesResponse.ok) {
const rolesData = await rolesResponse.json();
setRoles(rolesData.Roles);
@@ -99,7 +104,7 @@ export default function MapDetails() {
let allMapfixes: MapfixInfo[] = [];
let total = 0;
do {
const res = await fetch(`/api/mapfixes?Page=${page}&Limit=${limit}&TargetAssetID=${targetAssetId}`);
const res = await fetch(`/v1/mapfixes?Page=${page}&Limit=${limit}&TargetAssetID=${targetAssetId}`);
if (!res.ok) break;
const data = await res.json();
if (page === 1) total = data.Total;
@@ -150,7 +155,7 @@ export default function MapDetails() {
};
const handleSubmitMapfix = () => {
router.push(`/maps/${mapId}/fix`);
navigate(`/maps/${mapId}/fix`);
};
const handleCopyId = (idToCopy: string) => {
@@ -180,7 +185,7 @@ export default function MapDetails() {
<Typography variant="body1">{error}</Typography>
<Button
variant="contained"
onClick={() => router.push('/maps')}
onClick={() => navigate('/maps')}
sx={{ mt: 3 }}
>
Return to Maps
@@ -200,10 +205,10 @@ export default function MapDetails() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Link href="/maps" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/maps" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Maps</Typography>
</Link>
<Typography color="text.secondary">{loading ? "Loading..." : map?.DisplayName || "Map Details"}</Typography>
@@ -299,7 +304,7 @@ export default function MapDetails() {
<IconButton
size="small"
component="a"
href={`/api/maps/${mapId}/download`}
href={`/v1/maps/${mapId}/download`}
download={`${map?.DisplayName}.rbxm`}
sx={{ ml: 1 }}
>
@@ -322,15 +327,33 @@ export default function MapDetails() {
position: 'relative'
}}
>
<CardMedia
component="img"
image={`/thumbnails/asset/${map.ID}`}
alt={`Preview of map: ${map.DisplayName}`}
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
<Skeleton
variant="rectangular"
height={400}
animation="wave"
sx={{
height: 400,
objectFit: 'cover',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: thumbnailLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
/>
/>
<CardMedia
component="img"
image={thumbnailUrl || '/placeholder-map.png'}
alt={`Preview of map: ${map.DisplayName}`}
sx={{
height: 400,
objectFit: 'cover',
opacity: thumbnailLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
</Box>
</Paper>
</Grid>
@@ -391,7 +414,7 @@ export default function MapDetails() {
<Typography
variant="body2"
component={Link}
href={`/mapfixes/${showFix.ID}`}
to={`/mapfixes/${showFix.ID}`}
sx={{
textDecoration: 'underline',
cursor: 'pointer',

View File

@@ -1,8 +1,5 @@
"use client";
import {useState, useEffect} from "react";
import Image from "next/image";
import {useRouter} from "next/navigation";
import {useNavigate} from "react-router-dom";
import Webpage from "@/app/_components/webpage";
import {
Box,
@@ -16,18 +13,19 @@ import {
TextField,
InputAdornment,
Pagination,
CircularProgress,
FormControl,
InputLabel,
Select,
MenuItem,
SelectChangeEvent, Breadcrumbs
SelectChangeEvent,
Breadcrumbs,
Skeleton
} from "@mui/material";
import {Search as SearchIcon} from "@mui/icons-material";
import Link from "next/link";
import { Link } from "react-router-dom";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
import {useTitle} from "@/app/hooks/useTitle";
import {thumbnailLoader} from '@/app/lib/thumbnailLoader';
import {usePrefetchThumbnails, useAssetThumbnail} from "@/app/hooks/useThumbnails";
interface Map {
ID: number;
@@ -37,10 +35,99 @@ interface Map {
Date: number;
}
interface MapCardProps {
map: Map;
onClick: (mapId: number) => void;
formatDate: (timestamp: number) => string;
getGameName: (gameId: number) => string;
getGameLabelStyles: (gameId: number) => {
bgcolor: string;
color: string;
};
}
function MapCard({ map, onClick, formatDate, getGameName, getGameLabelStyles }: MapCardProps) {
const { thumbnailUrl, isLoading } = useAssetThumbnail(map.ID, '420x420');
return (
<Card
elevation={1}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'transform 0.2s, box-shadow 0.2s',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: 4,
}
}}
>
<CardActionArea onClick={() => onClick(map.ID)}>
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
<Skeleton
variant="rectangular"
height={180}
animation="wave"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: isLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
/>
<CardMedia
component="img"
image={thumbnailUrl || '/placeholder-map.png'}
alt={map.DisplayName}
sx={{
height: 180,
objectFit: 'cover',
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
<Box
position="absolute"
top={10}
right={10}
px={1}
py={0.5}
borderRadius={1}
fontSize="0.75rem"
fontWeight="bold"
sx={{
...getGameLabelStyles(map.GameID),
opacity: isLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out 0.1s',
}}
>
{getGameName(map.GameID)}
</Box>
</Box>
<CardContent>
<Typography variant="h6" component="h2" noWrap>
{map.DisplayName}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
By {map.Creator}
</Typography>
<Typography variant="caption" color="text.secondary">
Added {formatDate(map.Date)}
</Typography>
</CardContent>
</CardActionArea>
</Card>
);
}
export default function MapsPage() {
useTitle("Map Collection");
const router = useRouter();
const navigate = useNavigate();
const [maps, setMaps] = useState<Map[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
@@ -59,7 +146,7 @@ export default function MapsPage() {
let hasMore = true;
while (hasMore) {
const res = await fetch(`/api/maps?Page=${page}&Limit=${requestPageSize}`);
const res = await fetch(`/v1/maps?Page=${page}&Limit=${requestPageSize}`);
const data: Map[] = await res.json();
allMaps = [...allMaps, ...data];
hasMore = data.length === requestPageSize;
@@ -102,13 +189,19 @@ export default function MapsPage() {
currentPage * mapsPerPage
);
// Prefetch thumbnails for current page maps to batch them together
usePrefetchThumbnails(
currentMaps.map(map => ({ assetId: map.ID })),
'420x420'
);
const handlePageChange = (_event: React.ChangeEvent<unknown>, page: number) => {
setCurrentPage(page);
window.scrollTo({top: 0, behavior: 'smooth'});
};
const handleMapClick = (mapId: number) => {
router.push(`/maps/${mapId}`);
navigate(`/maps/${mapId}`);
};
const formatDate = (timestamp: number) => {
@@ -166,7 +259,7 @@ export default function MapsPage() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Typography color="text.secondary">Maps</Typography>
@@ -197,9 +290,27 @@ export default function MapsPage() {
/>
{loading ? (
<Box display="flex" justifyContent="center" my={8}>
<CircularProgress/>
</Box>
<Grid container spacing={3}>
{Array.from({ length: mapsPerPage }).map((_, index) => (
<Grid size={{ xs: 12, sm: 6, md: 4}} key={index}>
<Card
elevation={1}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
}}
>
<Skeleton variant="rectangular" height={180} animation="wave" />
<CardContent>
<Skeleton variant="text" width="80%" height={32} sx={{ mb: 1 }} />
<Skeleton variant="text" width="60%" height={20} sx={{ mb: 1 }} />
<Skeleton variant="text" width="40%" height={16} />
</CardContent>
</Card>
</Grid>
))}
</Grid>
) : (
<>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
@@ -227,62 +338,13 @@ export default function MapsPage() {
<Grid container spacing={3}>
{currentMaps.map((map) => (
<Grid size={{ xs: 12, sm: 6, md: 4}} key={map.ID}>
<Card
elevation={1}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'transform 0.2s, box-shadow 0.2s',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: 4,
}
}}
>
<CardActionArea onClick={() => handleMapClick(map.ID)}>
<CardMedia
component="div"
sx={{
position: 'relative',
height: 180,
backgroundColor: 'rgba(0,0,0,0.05)',
}}
>
<Box
position="absolute"
top={10}
right={10}
px={1}
py={0.5}
borderRadius={1}
fontSize="0.75rem"
fontWeight="bold"
{...getGameLabelStyles(map.GameID)}
>
{getGameName(map.GameID)}
</Box>
<Image
loader={thumbnailLoader}
src={`/thumbnails/asset/${map.ID}`}
alt={map.DisplayName}
fill
style={{objectFit: 'cover'}}
/>
</CardMedia>
<CardContent>
<Typography variant="h6" component="h2" noWrap>
{map.DisplayName}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
By {map.Creator}
</Typography>
<Typography variant="caption" color="text.secondary">
Added {formatDate(map.Date)}
</Typography>
</CardContent>
</CardActionArea>
</Card>
<MapCard
map={map}
onClick={handleMapClick}
formatDate={formatDate}
getGameName={getGameName}
getGameLabelStyles={getGameLabelStyles}
/>
</Grid>
))}
</Grid>

View File

@@ -0,0 +1,191 @@
import { Box, Container, Typography, Button } from "@mui/material";
import { Link } from "react-router-dom";
import Webpage from "@/app/_components/webpage";
import { useTitle } from "@/app/hooks/useTitle";
import HomeIcon from "@mui/icons-material/Home";
import MapIcon from "@mui/icons-material/Map";
export default function NotFound() {
useTitle("404 - Page Not Found");
return (
<Webpage>
<Box sx={{ width: '100%', bgcolor: 'background.default' }}>
{/* 404 Hero Section */}
<Box
sx={{
position: 'relative',
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
background: 'linear-gradient(to bottom, #0a0a0a 0%, #0f0f0f 100%)',
}}
>
{/* Subtle Gradient Background */}
<Box
sx={{
position: 'absolute',
top: '20%',
right: '30%',
width: '500px',
height: '500px',
background: 'radial-gradient(circle, rgba(239, 68, 68, 0.1) 0%, transparent 70%)',
borderRadius: '50%',
filter: 'blur(80px)',
}}
/>
<Box
sx={{
position: 'absolute',
bottom: '20%',
left: '25%',
width: '450px',
height: '450px',
background: 'radial-gradient(circle, rgba(59, 130, 246, 0.08) 0%, transparent 70%)',
borderRadius: '50%',
filter: 'blur(80px)',
}}
/>
<Container maxWidth="md" sx={{ position: 'relative', zIndex: 1, py: 8 }}>
<Box textAlign="center">
{/* 404 Number */}
<Typography
variant="h1"
sx={{
fontSize: { xs: '6rem', sm: '8rem', md: '10rem' },
fontWeight: 800,
lineHeight: 1,
mb: 2,
letterSpacing: '-0.04em',
background: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
}}
>
404
</Typography>
{/* Main Message */}
<Typography
variant="h2"
sx={{
fontWeight: 700,
mb: 2,
letterSpacing: '-0.02em',
fontSize: { xs: '2rem', sm: '2.5rem', md: '3rem' },
}}
>
Page Not Found
</Typography>
{/* Subtext */}
<Typography
variant="h6"
sx={{
color: 'text.secondary',
mb: 6,
maxWidth: '600px',
mx: 'auto',
lineHeight: 1.7,
fontWeight: 400,
fontSize: { xs: '1rem', md: '1.125rem' },
}}
>
Looks like this page doesn't exist. The page you're looking for might have been removed, renamed, or never existed in the first place.
</Typography>
{/* Action Buttons */}
<Box
display="flex"
gap={2.5}
justifyContent="center"
flexWrap="wrap"
mb={8}
>
<Button
component={Link}
to="/"
variant="contained"
size="large"
startIcon={<HomeIcon />}
sx={{
fontSize: '1rem',
px: 4,
py: 1.5,
}}
>
Back to Home
</Button>
<Button
component={Link}
to="/maps"
variant="outlined"
size="large"
startIcon={<MapIcon />}
sx={{
fontSize: '1rem',
px: 4,
py: 1.5,
}}
>
Browse Maps
</Button>
</Box>
{/* Quick Links */}
<Box sx={{ mt: 8 }}>
<Typography
variant="body2"
sx={{
color: 'text.secondary',
mb: 3,
fontSize: '0.875rem',
textTransform: 'uppercase',
letterSpacing: '0.1em',
fontWeight: 600,
}}
>
Quick Links
</Typography>
<Box
sx={{
display: 'flex',
gap: 3,
justifyContent: 'center',
flexWrap: 'wrap',
}}
>
{[
{ label: 'Submissions', path: '/submissions' },
{ label: 'Map Fixes', path: '/mapfixes' },
{ label: 'Submit Map', path: '/submit' },
].map((link) => (
<Button
key={link.path}
component={Link}
to={link.path}
sx={{
color: 'text.secondary',
textTransform: 'none',
fontSize: '1rem',
'&:hover': {
color: 'primary.main',
background: 'rgba(59, 130, 246, 0.1)',
},
}}
>
{link.label}
</Button>
))}
</Box>
</Box>
</Box>
</Container>
</Box>
</Box>
</Webpage>
);
}

View File

@@ -1,7 +1,5 @@
"use client";
import {useEffect, useState, useRef, ReactElement} from "react";
import { useParams, useRouter } from "next/navigation";
import { useParams, useNavigate } from "react-router-dom";
import {
CircularProgress,
Typography,
@@ -13,7 +11,11 @@ import {
Divider,
Alert,
Collapse,
IconButton
IconButton,
Fade,
Grow,
Slide,
keyframes
} from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
@@ -23,6 +25,67 @@ import PendingIcon from '@mui/icons-material/Pending';
import Webpage from "@/app/_components/webpage";
import {useTitle} from "@/app/hooks/useTitle";
const pulse = keyframes`
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
`;
const spin = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const slideInUp = keyframes`
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
`;
const successPop = keyframes`
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
`;
const errorShake = keyframes`
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-5px);
}
20%, 40%, 60%, 80% {
transform: translateX(5px);
}
`;
interface Operation {
OperationID: number;
Status: number;
@@ -33,7 +96,7 @@ interface Operation {
}
export default function OperationStatusPage() {
const router = useRouter();
const navigate = useNavigate();
const { operationId } = useParams();
const [loading, setLoading] = useState(true);
@@ -49,7 +112,7 @@ export default function OperationStatusPage() {
const fetchOperation = async () => {
try {
const response = await fetch(`/api/operations/${operationId}`);
const response = await fetch(`/v1/operations/${operationId}`);
if (!response.ok) throw new Error("Failed to fetch operation");
@@ -72,13 +135,12 @@ export default function OperationStatusPage() {
};
fetchOperation();
if (!intervalRef.current) {
intervalRef.current = setInterval(fetchOperation, 1000);
}
intervalRef.current = setInterval(fetchOperation, 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [operationId]);
@@ -134,12 +196,27 @@ export default function OperationStatusPage() {
}
};
const getStatusAnimation = (status: number) => {
switch (status) {
case 0:
return pulse;
case 1:
return successPop;
case 2:
return errorShake;
default:
return undefined;
}
};
return (
<Webpage>
<Container maxWidth="md" sx={{ py: 6 }}>
<Typography variant="h4" component="h1" fontWeight="bold" mb={4}>
Operation Status
</Typography>
<Fade in timeout={500}>
<Typography variant="h4" component="h1" fontWeight="bold" mb={4}>
Operation Status
</Typography>
</Fade>
{loading ? (
<Box display="flex" flexDirection="column" alignItems="center" my={8}>
@@ -149,33 +226,47 @@ export default function OperationStatusPage() {
</Typography>
</Box>
) : error ? (
<Alert severity="error" sx={{ my: 2 }}>
<Typography variant="body1">{error}</Typography>
</Alert>
<Slide direction="up" in mountOnEnter unmountOnExit>
<Alert severity="error" sx={{ my: 2 }}>
<Typography variant="body1">{error}</Typography>
</Alert>
</Slide>
) : operation ? (
<Paper
elevation={3}
sx={{
p: 3,
borderRadius: 2,
border: 1,
borderColor: 'divider'
}}
>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Typography variant="h5">
Operation #{operation.OperationID}
</Typography>
<Chip
icon={getStatusIcon(operation.Status)}
label={getStatusText(operation.Status)}
color={getStatusColor(operation.Status) as "success" | "warning" | "error" | "default"}
variant="filled"
sx={{ fontWeight: 'bold', px: 1 }}
/>
</Box>
<Grow in timeout={600}>
<Box>
<Paper
elevation={3}
sx={{
p: 3,
borderRadius: 2,
border: 1,
borderColor: 'divider',
animation: `${slideInUp} 0.5s ease-out`
}}
>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Typography variant="h5">
Operation #{operation.OperationID}
</Typography>
<Chip
icon={getStatusIcon(operation.Status)}
label={getStatusText(operation.Status)}
color={getStatusColor(operation.Status) as "success" | "warning" | "error" | "default"}
variant="filled"
sx={{
fontWeight: 'bold',
px: 1,
animation: operation.Status === 0
? `${pulse} 2s ease-in-out infinite`
: `${getStatusAnimation(operation.Status)} 0.5s ease-out`,
'& .MuiChip-icon': {
animation: operation.Status === 0 ? `${spin} 2s linear infinite` : 'none'
}
}}
/>
</Box>
<Divider sx={{ my: 2 }} />
<Divider sx={{ my: 2 }} />
<Box sx={{ mb: 3 }}>
<Typography variant="body1" color="text.secondary" gutterBottom>
@@ -233,24 +324,35 @@ export default function OperationStatusPage() {
)}
</Box>
{operation.Status === 1 && (
<Box sx={{ mt: 4, textAlign: 'center' }}>
<Button
variant="contained"
color="primary"
size="large"
onClick={() => router.push(operation.Path)}
startIcon={<CheckCircleIcon />}
>
Next Step
</Button>
</Box>
)}
</Paper>
{operation.Status === 1 && (
<Box sx={{ mt: 4, textAlign: 'center' }}>
<Button
variant="contained"
color="primary"
size="large"
onClick={() => navigate(operation.Path)}
startIcon={<CheckCircleIcon />}
sx={{
animation: `${successPop} 0.6s ease-out`,
transition: 'transform 0.2s',
'&:hover': {
transform: 'scale(1.05)'
}
}}
>
Next Step
</Button>
</Box>
)}
</Paper>
</Box>
</Grow>
) : (
<Alert severity="info" sx={{ my: 2 }}>
<Typography variant="body1">No operation found with ID: {operationId}</Typography>
</Alert>
<Fade in>
<Alert severity="info" sx={{ my: 2 }}>
<Typography variant="body1">No operation found with ID: {operationId}</Typography>
</Alert>
</Fade>
)}
</Container>
</Webpage>

View File

@@ -1,6 +1,4 @@
'use client'
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import {MapfixInfo, MapfixList} from "./ts/Mapfix";
import { MapCard } from "./_components/mapCard";
import Webpage from "./_components/webpage";
@@ -10,30 +8,42 @@ import {
Container,
CircularProgress,
Typography,
Paper,
Button,
} from "@mui/material";
import Link from "next/link";
import { Link } from "react-router-dom";
import {SubmissionInfo, SubmissionList} from "@/app/ts/Submission";
import {Carousel} from "@/app/_components/carousel";
import {useTitle} from "@/app/hooks/useTitle";
import {usePrefetchThumbnails} from "@/app/hooks/useThumbnails";
import {Stats} from "@/app/ts/Stats";
import BuildIcon from "@mui/icons-material/Build";
import RateReviewIcon from "@mui/icons-material/RateReview";
import ListIcon from "@mui/icons-material/List";
import MapIcon from "@mui/icons-material/Map";
import RocketLaunchIcon from "@mui/icons-material/RocketLaunch";
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents";
export default function Home() {
useTitle("Home");
const [mapfixes, setMapfixes] = useState<MapfixList | null>(null);
const [submissions, setSubmissions] = useState<SubmissionList | null>(null);
const [stats, setStats] = useState<Stats | null>(null);
const [isLoadingMapfixes, setIsLoadingMapfixes] = useState<boolean>(false);
const [isLoadingSubmissions, setIsLoadingSubmissions] = useState<boolean>(false);
const itemsPerSection: number = 8; // Show more items for the carousel
const [isLoadingStats, setIsLoadingStats] = useState<boolean>(false);
const [currentStatIndex, setCurrentStatIndex] = useState<number>(0);
const itemsPerSection: number = 8;
useEffect(() => {
const mapfixController = new AbortController();
const submissionsController = new AbortController();
const statsController = new AbortController();
async function fetchMapFixes(): Promise<void> {
setIsLoadingMapfixes(true);
try {
const res = await fetch(`/api/mapfixes?Page=1&Limit=${itemsPerSection}&Sort=${ListSortConstants.ListSortDateDescending}`, {
const res = await fetch(`/v1/mapfixes?Page=1&Limit=${itemsPerSection}&Sort=${ListSortConstants.ListSortDateDescending}`, {
signal: mapfixController.signal,
});
if (res.ok) {
@@ -50,7 +60,7 @@ export default function Home() {
async function fetchSubmissions(): Promise<void> {
setIsLoadingSubmissions(true);
try {
const res = await fetch(`/api/submissions?Page=1&Limit=${itemsPerSection}&Sort=${ListSortConstants.ListSortDateDescending}`, {
const res = await fetch(`/v1/submissions?Page=1&Limit=${itemsPerSection}&Sort=${ListSortConstants.ListSortDateDescending}`, {
signal: submissionsController.signal,
});
if (res.ok) {
@@ -64,34 +74,68 @@ export default function Home() {
}
}
async function fetchStats(): Promise<void> {
setIsLoadingStats(true);
try {
const res = await fetch(`/v1/stats`, {
signal: statsController.signal,
});
if (res.ok) {
const data: Stats = await res.json();
setStats(data);
}
} catch (error) {
console.error("Failed to fetch stats:", error);
} finally {
setIsLoadingStats(false);
}
}
fetchMapFixes();
fetchSubmissions();
fetchStats();
return () => {
mapfixController.abort();
submissionsController.abort();
statsController.abort();
};
}, []);
const isLoading: boolean = isLoadingMapfixes || isLoadingSubmissions;
// Cycle through featured stats every 3 seconds
useEffect(() => {
const interval = setInterval(() => {
setCurrentStatIndex((prev) => (prev + 1) % 6);
}, 3000);
return () => clearInterval(interval);
}, []);
if (isLoading && (!mapfixes || !submissions)) {
// Prefetch thumbnails for all items to batch them together
const allItems = [
...(mapfixes?.Mapfixes || []).map((m: MapfixInfo) => ({ assetId: m.AssetID, userId: m.Submitter })),
...(submissions?.Submissions || []).map((s: SubmissionInfo) => ({ assetId: s.AssetID, userId: s.Submitter })),
];
usePrefetchThumbnails(allItems);
const isLoading: boolean = isLoadingMapfixes || isLoadingSubmissions || isLoadingStats;
if (isLoading && (!mapfixes || !submissions || !stats)) {
return <Webpage>
<main
style={{
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
minHeight: 'calc(100vh - 200px)',
}}
>
<Box display="flex" flexDirection="column" alignItems="center">
<CircularProgress/>
<Typography variant="body1" style={{marginTop: '1rem'}}>
<Box display="flex" flexDirection="column" alignItems="center" gap={2}>
<CircularProgress size={60} thickness={4} sx={{ color: 'primary.main' }}/>
<Typography variant="h6" color="text.secondary">
Loading content...
</Typography>
</Box>
</main>
</Box>
</Webpage>;
}
@@ -127,105 +171,699 @@ export default function Home() {
/>
);
// Get stats from API
const totalSubmissions = stats?.TotalSubmissions || 0;
const totalMapfixes = stats?.TotalMapfixes || 0;
const releasedSubmissions = stats?.ReleasedSubmissions || 0;
const releasedMapfixes = stats?.ReleasedMapfixes || 0;
const submittedSubmissions = stats?.SubmittedSubmissions || 0;
const submittedMapfixes = stats?.SubmittedMapfixes || 0;
// Define all stats for cycling
const allStats = [
{
icon: <MapIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: totalSubmissions,
label: 'Total Submissions',
sublabel: 'Total maps submitted by the community',
color: '#3b82f6',
gradient: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
},
{
icon: <BuildIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: totalMapfixes,
label: 'Total Map Fixes',
sublabel: 'Total map fixes submitted by the community',
color: '#8b5cf6',
gradient: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)',
},
{
icon: <EmojiEventsIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: releasedSubmissions + releasedMapfixes,
label: 'Total Released',
sublabel: 'Maps & fixes that have been released to the game',
color: '#10b981',
gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
},
{
icon: <EmojiEventsIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: releasedSubmissions,
label: 'Released Submissions',
sublabel: 'Approved maps that have been published to the game',
color: '#10b981',
gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
},
{
icon: <EmojiEventsIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: releasedMapfixes,
label: 'Released Fixes',
sublabel: 'Approved fixes that have been released to the game',
color: '#059669',
gradient: 'linear-gradient(135deg, #059669 0%, #047857 100%)',
},
{
icon: <RateReviewIcon sx={{ fontSize: { xs: 48, md: 64 } }} />,
value: submittedSubmissions + submittedMapfixes,
label: 'Under Review',
sublabel: 'Pending approval fixes & submissions',
color: '#f59e0b',
gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
},
];
const currentStat = allStats[currentStatIndex];
return (
<Webpage>
<Container maxWidth="lg" sx={{ py: 6 }}>
<main
style={{
<Box sx={{ width: '100%', bgcolor: 'background.default' }}>
{/* Hero Section */}
<Box
sx={{
position: 'relative',
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '1rem',
width: '100%',
maxWidth: '100vw',
boxSizing: 'border-box',
overflowX: 'hidden'
alignItems: 'center',
overflow: 'hidden',
background: 'radial-gradient(ellipse at top, #0f1419 0%, #0a0a0a 50%, #000000 100%)',
}}
>
<Typography variant="h3" component="h1" fontWeight="bold" mb={5}>
Welcome to the Maps Service!
</Typography>
<Paper
elevation={2}
{/* Animated Background Elements */}
<Box
sx={{
p: 4,
mb: 6,
borderRadius: 2,
background: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)',
color: 'white'
position: 'absolute',
top: '5%',
right: '15%',
width: { xs: '400px', md: '600px' },
height: { xs: '400px', md: '600px' },
background: 'radial-gradient(circle, rgba(59, 130, 246, 0.15) 0%, transparent 70%)',
borderRadius: '50%',
filter: 'blur(80px)',
animation: 'float 25s ease-in-out infinite',
'@keyframes float': {
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
'50%': { transform: 'translate(40px, -40px) scale(1.2)' },
},
}}
>
<Typography variant="h4" component="h2" gutterBottom>
Contribute to the community
</Typography>
<Typography variant="body1" paragraph>
Help improve maps by submitting fixes or creating new maps submissions for the community.
</Typography>
<Box display="flex" gap={2}>
<Link href="/submit" style={{ textDecoration: 'none' }}>
<Box
component="button"
/>
<Box
sx={{
position: 'absolute',
bottom: '10%',
left: '10%',
width: { xs: '350px', md: '500px' },
height: { xs: '350px', md: '500px' },
background: 'radial-gradient(circle, rgba(139, 92, 246, 0.12) 0%, transparent 70%)',
borderRadius: '50%',
filter: 'blur(80px)',
animation: 'float-reverse 30s ease-in-out infinite',
'@keyframes float-reverse': {
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
'50%': { transform: 'translate(-30px, 30px) scale(1.15)' },
},
}}
/>
{/* Subtle Grid Overlay */}
<Box
sx={{
position: 'absolute',
inset: 0,
backgroundImage: `
linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px)
`,
backgroundSize: '60px 60px',
maskImage: 'radial-gradient(ellipse at center, black 20%, transparent 70%)',
WebkitMaskImage: 'radial-gradient(ellipse at center, black 20%, transparent 70%)',
}}
/>
{/* Accent Lines */}
<Box
sx={{
position: 'absolute',
top: '20%',
left: 0,
right: 0,
height: '1px',
background: 'linear-gradient(90deg, transparent 0%, rgba(59, 130, 246, 0.3) 50%, transparent 100%)',
opacity: 0.5,
}}
/>
<Box
sx={{
position: 'absolute',
bottom: '30%',
left: 0,
right: 0,
height: '1px',
background: 'linear-gradient(90deg, transparent 0%, rgba(139, 92, 246, 0.3) 50%, transparent 100%)',
opacity: 0.5,
}}
/>
<Container maxWidth="xl" sx={{ position: 'relative', zIndex: 1, py: { xs: 6, md: 8 } }}>
<Box textAlign="center">
{/* Main Headline */}
<Box
sx={{
mb: { xs: 5, md: 6 },
animation: 'fadeInUp 0.9s ease-out',
'@keyframes fadeInUp': {
from: { opacity: 0, transform: 'translateY(30px)' },
to: { opacity: 1, transform: 'translateY(0)' },
},
}}
>
<Typography
variant="overline"
sx={{
backgroundColor: 'white',
color: '#2196F3',
border: 'none',
borderRadius: 1,
px: 3,
py: 1.5,
fontWeight: 'bold',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
}
fontSize: { xs: '0.75rem', md: '0.875rem' },
fontWeight: 700,
letterSpacing: '0.2em',
textTransform: 'uppercase',
color: 'primary.main',
mb: 3,
display: 'block',
opacity: 0.9,
}}
>
Submit Map
</Box>
</Link>
<Link href="/maps" style={{ textDecoration: 'none' }}>
<Box
component="button"
Welcome to the Community
</Typography>
<Typography
variant="h1"
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: 'white',
border: '1px solid white',
borderRadius: 1,
px: 3,
py: 1.5,
fontWeight: 'bold',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.3)',
}
fontSize: { xs: '3.5rem', sm: '5rem', md: '7rem', lg: '8rem' },
fontWeight: 900,
lineHeight: 0.95,
mb: 2,
letterSpacing: '-0.04em',
background: 'linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #c084fc 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
textShadow: '0 0 80px rgba(59, 130, 246, 0.3)',
}}
>
Create Map Fix
StrafesNET
</Typography>
<Typography
variant="h2"
sx={{
color: 'text.primary',
fontSize: { xs: '1.75rem', sm: '2.25rem', md: '3rem' },
fontWeight: 700,
letterSpacing: '-0.02em',
mb: 3,
opacity: 0.95,
}}
>
Community Maps Hub
</Typography>
<Typography
variant="h6"
sx={{
color: 'text.secondary',
mb: 5,
lineHeight: 1.75,
fontWeight: 400,
fontSize: { xs: '1rem', md: '1.15rem' },
maxWidth: '700px',
mx: 'auto',
opacity: 0.85,
}}
>
Create, test, and improve maps for the StrafesNET community.
Submit your creations or help fix existing maps.
</Typography>
</Box>
{/* CTA Buttons - Moved up for better hierarchy */}
<Box
display="flex"
gap={3}
justifyContent="center"
flexWrap="wrap"
sx={{
mb: { xs: 7, md: 9 },
animation: 'fadeInUp 1s ease-out 0.2s both',
}}
>
<Button
component={Link}
to="/submit"
variant="contained"
size="large"
startIcon={<RocketLaunchIcon />}
sx={{
fontSize: { xs: '1rem', md: '1.1rem' },
px: { xs: 4, md: 5 },
py: { xs: 1.75, md: 2.25 },
fontWeight: 700,
background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
boxShadow: '0 8px 32px rgba(59, 130, 246, 0.4)',
borderRadius: 2,
textTransform: 'none',
'&:hover': {
background: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)',
boxShadow: '0 12px 40px rgba(59, 130, 246, 0.6)',
transform: 'translateY(-2px)',
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
Submit Your Map
</Button>
<Button
component={Link}
to="/maps"
variant="outlined"
size="large"
startIcon={<BuildIcon />}
sx={{
fontSize: { xs: '1rem', md: '1.1rem' },
px: { xs: 4, md: 5 },
py: { xs: 1.75, md: 2.25 },
fontWeight: 700,
borderWidth: 2,
borderColor: 'rgba(139, 92, 246, 0.5)',
color: '#a78bfa',
borderRadius: 2,
textTransform: 'none',
'&:hover': {
borderWidth: 2,
borderColor: '#a78bfa',
background: 'rgba(139, 92, 246, 0.1)',
transform: 'translateY(-2px)',
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
Submit a Fix
</Button>
</Box>
{/* Stats Section - Completely Redesigned */}
<Box
sx={{
animation: 'fadeIn 1.1s ease-out 0.4s both',
'@keyframes fadeIn': {
from: { opacity: 0 },
to: { opacity: 1 },
},
}}
>
{/* Stats Grid */}
<Box
sx={{
display: 'grid',
gridTemplateColumns: {
xs: 'repeat(2, 1fr)',
sm: 'repeat(3, 1fr)',
md: 'repeat(6, 1fr)'
},
gap: { xs: 2, sm: 3, md: 4 },
maxWidth: '1200px',
mx: 'auto',
mb: 4,
}}
>
{allStats.map((stat, index) => (
<Box
key={index}
onClick={() => setCurrentStatIndex(index)}
sx={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1.5,
p: { xs: 2.5, md: 3 },
cursor: 'pointer',
background: currentStatIndex === index
? `linear-gradient(135deg, ${stat.color}15 0%, ${stat.color}08 100%)`
: 'rgba(17, 17, 17, 0.4)',
backdropFilter: 'blur(10px)',
border: currentStatIndex === index
? `1px solid ${stat.color}40`
: '1px solid rgba(255, 255, 255, 0.05)',
borderRadius: 3,
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
'&::before': currentStatIndex === index ? {
content: '""',
position: 'absolute',
inset: -1,
background: stat.gradient,
borderRadius: 3,
opacity: 0.1,
zIndex: -1,
} : {},
'&:hover': {
transform: 'translateY(-8px) scale(1.02)',
background: `linear-gradient(135deg, ${stat.color}20 0%, ${stat.color}10 100%)`,
borderColor: `${stat.color}60`,
boxShadow: `0 20px 40px ${stat.color}30`,
},
}}
>
{/* Icon */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: { xs: 50, md: 60 },
height: { xs: 50, md: 60 },
borderRadius: '50%',
background: currentStatIndex === index
? `linear-gradient(135deg, ${stat.color}25 0%, ${stat.color}15 100%)`
: `${stat.color}10`,
color: stat.color,
transition: 'all 0.3s',
boxShadow: currentStatIndex === index
? `0 0 30px ${stat.color}40`
: 'none',
}}
>
{React.cloneElement(stat.icon, {
sx: { fontSize: { xs: 28, md: 32 } }
})}
</Box>
{/* Value */}
<Typography
variant="h4"
sx={{
fontWeight: 900,
fontSize: { xs: '1.75rem', md: '2.25rem' },
color: currentStatIndex === index ? stat.color : 'text.primary',
letterSpacing: '-0.03em',
transition: 'color 0.3s',
lineHeight: 1,
}}
>
{stat.value}
</Typography>
{/* Label */}
<Typography
variant="caption"
sx={{
color: currentStatIndex === index ? 'text.primary' : 'text.secondary',
fontSize: { xs: '0.7rem', md: '0.75rem' },
fontWeight: 600,
textAlign: 'center',
textTransform: 'uppercase',
letterSpacing: '0.08em',
lineHeight: 1.3,
transition: 'color 0.3s',
opacity: currentStatIndex === index ? 1 : 0.7,
}}
>
{stat.label}
</Typography>
</Box>
))}
</Box>
</Link>
{/* Featured Stat Description */}
<Box
key={currentStatIndex}
sx={{
animation: 'statTextFade 0.5s ease-out',
'@keyframes statTextFade': {
'0%': { opacity: 0, transform: 'translateY(5px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
},
}}
>
<Typography
variant="body1"
sx={{
color: 'text.secondary',
fontSize: { xs: '0.9rem', md: '1rem' },
fontWeight: 500,
maxWidth: '600px',
mx: 'auto',
opacity: 0.8,
}}
>
{currentStat.sublabel}
</Typography>
</Box>
</Box>
</Box>
</Paper>
</Container>
</Box>
{/* Submissions Carousel */}
{/* Main Content */}
<Container maxWidth="lg" sx={{ py: 10 }}>
{/* Latest Submissions */}
{submissions && (
<Carousel<SubmissionInfo>
title="Recent Submissions"
items={submissions.Submissions}
renderItem={renderSubmissionCard}
viewAllLink="/submissions"
/>
<Box sx={{ mb: 12 }}>
<Box sx={{ mb: 5 }}>
<Typography
variant="h2"
sx={{
fontWeight: 700,
mb: 1.5,
letterSpacing: '-0.02em',
fontSize: { xs: '2rem', md: '2.75rem' },
}}
>
Latest Submissions
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ maxWidth: '600px' }}
>
Discover the newest custom maps created by the community
</Typography>
</Box>
<Carousel<SubmissionInfo>
title=""
items={submissions.Submissions}
renderItem={renderSubmissionCard}
viewAllLink="/submissions"
/>
</Box>
)}
{/* Map Fixes Carousel */}
{/* Recent Fixes */}
{mapfixes && (
<Carousel<MapfixInfo>
title="Recent Map Fixes"
items={mapfixes.Mapfixes}
renderItem={renderMapfixCard}
viewAllLink="/mapfixes"
/>
<Box sx={{ mb: 12 }}>
<Box sx={{ mb: 5 }}>
<Typography
variant="h2"
sx={{
fontWeight: 700,
mb: 1.5,
letterSpacing: '-0.02em',
fontSize: { xs: '2rem', md: '2.75rem' },
}}
>
Recent Fixes
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ maxWidth: '600px' }}
>
Community-created map fixes and improvements
</Typography>
</Box>
<Carousel<MapfixInfo>
title=""
items={mapfixes.Mapfixes}
renderItem={renderMapfixCard}
viewAllLink="/mapfixes"
/>
</Box>
)}
</main>
</Container>
{/* Feature Showcase Section */}
<Box sx={{ mb: 10 }}>
<Box sx={{ mb: 5 }}>
<Typography
variant="h2"
sx={{
fontWeight: 700,
mb: 1.5,
letterSpacing: '-0.02em',
fontSize: { xs: '2rem', md: '2.75rem' },
}}
>
How It Works
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ maxWidth: '600px' }}
>
Join the community and start contributing today
</Typography>
</Box>
<Box
sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', md: 'repeat(3, 1fr)' },
gap: 3,
}}
>
{[
{
icon: <RocketLaunchIcon sx={{ fontSize: 48 }} />,
title: 'Submit Maps',
description: 'Upload your custom bhop and surf maps for review. Maps are evaluated by moderators before being added to the game.',
link: '/submit',
color: '#3b82f6',
},
{
icon: <BuildIcon sx={{ fontSize: 48 }} />,
title: 'Submit Fixes',
description: 'Found bugs or issues in existing maps? Submit fixed versions to improve map quality for all players.',
link: '/mapfixes',
color: '#8b5cf6',
},
{
icon: <ListIcon sx={{ fontSize: 48 }} />,
title: 'View Submissions',
description: 'Browse all pending and approved submissions currently in the review queue. Track submission status and feedback.',
link: '/submissions',
color: '#10b981',
},
].map((card, index) => (
<Box
key={index}
component={Link}
to={card.link}
sx={{
p: 5,
background: 'rgba(23, 23, 23, 0.5)',
borderRadius: 2,
border: '1px solid rgba(255, 255, 255, 0.08)',
textDecoration: 'none',
color: 'inherit',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
transform: 'translateY(-4px)',
borderColor: `${card.color}40`,
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4)',
'& .icon-box': {
background: `${card.color}30`,
},
},
}}
>
<Box
className="icon-box"
sx={{
display: 'inline-flex',
p: 2,
borderRadius: 1.5,
background: `${card.color}20`,
mb: 3,
color: card.color,
transition: 'background 0.3s',
}}
>
{card.icon}
</Box>
<Typography
variant="h5"
sx={{
fontWeight: 600,
mb: 1.5,
letterSpacing: '-0.01em',
}}
>
{card.title}
</Typography>
<Typography
variant="body2"
sx={{
color: 'text.secondary',
lineHeight: 1.7,
}}
>
{card.description}
</Typography>
</Box>
))}
</Box>
</Box>
</Container>
{/* Bottom CTA Section */}
<Box
sx={{
position: 'relative',
py: 12,
background: '#0f0f0f',
borderTop: '1px solid rgba(255, 255, 255, 0.08)',
}}
>
<Container maxWidth="md" sx={{ position: 'relative', textAlign: 'center' }}>
<Typography
variant="h2"
sx={{
fontWeight: 700,
mb: 2,
letterSpacing: '-0.025em',
fontSize: { xs: '2.25rem', md: '3rem' },
}}
>
Get Started
</Typography>
<Typography
variant="body1"
sx={{
color: 'text.secondary',
mb: 5,
lineHeight: 1.7,
fontSize: '1.125rem',
}}
>
Submit your maps or browse existing submissions to see what's currently in review.
</Typography>
<Box display="flex" gap={2.5} justifyContent="center" flexWrap="wrap">
<Button
component={Link}
to="/submit"
variant="contained"
size="large"
startIcon={<RocketLaunchIcon />}
sx={{
fontSize: '1rem',
px: 4,
py: 1.5,
}}
>
Submit a Map
</Button>
<Button
component={Link}
to="/submissions"
variant="outlined"
size="large"
startIcon={<MapIcon />}
sx={{
fontSize: '1rem',
px: 4,
py: 1.5,
}}
>
View Submissions
</Button>
</Box>
</Container>
</Box>
</Box>
</Webpage>
);
}
}

View File

@@ -1,31 +0,0 @@
import { NextResponse } from 'next/server';
export async function GET(
request: Request,
{ params }: { params: Promise<{ userId: string }> }
) {
const { userId } = await params;
if (!userId) {
return NextResponse.json({ error: 'User ID is required' }, { status: 400 });
}
try {
const apiResponse = await fetch(`https://users.roblox.com/v1/users/${userId}`);
if (!apiResponse.ok) {
const errorData = await apiResponse.text();
return NextResponse.json({ error: `Failed to fetch from Roblox API: ${errorData}` }, { status: apiResponse.status });
}
const data = await apiResponse.json();
// Add caching headers to the response
const headers = new Headers();
headers.set('Cache-Control', 'public, max-age=3600, s-maxage=3600'); // Cache for 1 hour
return NextResponse.json(data, { headers });
} catch {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}

View File

@@ -1,8 +1,8 @@
"use client";
import Webpage from "@/app/_components/webpage";
import { useParams, useRouter } from "next/navigation";
import { useParams, useNavigate } from "react-router-dom";
import {useState} from "react";
import Link from "next/link";
import { Link } from "react-router-dom";
import { useAssetThumbnail } from "@/app/hooks/useThumbnails";
// MUI Components
import {
@@ -35,7 +35,7 @@ interface SnackbarState {
export default function SubmissionDetailsPage() {
const { submissionId } = useParams<{ submissionId: string }>();
const router = useRouter();
const navigate = useNavigate();
const [newComment, setNewComment] = useState("");
const [snackbar, setSnackbar] = useState<SnackbarState>({
open: false,
@@ -70,16 +70,22 @@ export default function SubmissionDetailsPage() {
refreshData
} = useReviewData({
itemType: 'submissions',
itemId: submissionId
itemId: submissionId!
});
const submission = submissionData as SubmissionInfo;
useTitle(submission ? `${submission.DisplayName} Submission` : 'Loading Submission...');
// Use thumbnail hook for the submission image
const { thumbnailUrl, isLoading: thumbnailLoading } = useAssetThumbnail(
submission?.AssetID,
'420x420'
);
// Handle review button actions
async function handleReviewAction(action: string, submissionId: number) {
try {
const response = await fetch(`/api/submissions/${submissionId}/status/${action}`, {
const response = await fetch(`/v1/submissions/${submissionId}/status/${action}`, {
method: "POST",
headers: {
"Content-type": "application/json",
@@ -118,7 +124,7 @@ export default function SubmissionDetailsPage() {
}
try {
const response = await fetch(`/api/submissions/${submissionId}/comment`, {
const response = await fetch(`/v1/submissions/${submissionId}/comment`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
@@ -177,7 +183,7 @@ export default function SubmissionDetailsPage() {
title="Error Loading Submission"
message={error || "Submission not found"}
buttonText="Return to Submissions"
onButtonClick={() => router.push('/submissions')}
onButtonClick={() => navigate('/submissions')}
/>
);
}
@@ -190,10 +196,10 @@ export default function SubmissionDetailsPage() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Link href="/submissions" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/submissions" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Submissions</Typography>
</Link>
<Typography color="text.secondary">{submission.DisplayName}</Typography>
@@ -204,12 +210,33 @@ export default function SubmissionDetailsPage() {
<Grid size={{ xs: 12, md: 4}}>
<Paper elevation={3} sx={{ borderRadius: 2, overflow: 'hidden', mb: 3 }}>
{submission.AssetID ? (
<CardMedia
component="img"
image={`/thumbnails/asset/${submission.AssetID}`}
alt="Map Thumbnail"
sx={{ width: '100%', aspectRatio: '1/1', objectFit: 'cover' }}
/>
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
<Skeleton
variant="rectangular"
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
aspectRatio: '1/1',
opacity: thumbnailLoading ? 1 : 0,
transition: 'opacity 0.3s ease-in-out',
}}
animation="wave"
/>
<CardMedia
component="img"
image={thumbnailUrl || '/placeholder-map.png'}
alt="Map Thumbnail"
sx={{
width: '100%',
aspectRatio: '1/1',
objectFit: 'cover',
opacity: thumbnailLoading ? 0 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
</Box>
) : (
<Box
sx={{

View File

@@ -1,5 +1,3 @@
"use client"
import { useState, useEffect } from "react";
import { SubmissionList } from "../ts/Submission";
import { MapCard } from "../_components/mapCard";
@@ -8,12 +6,14 @@ import { ListSortConstants } from "../ts/Sort";
import {
Box,
Breadcrumbs,
CircularProgress,
Card,
CardContent,
Container,
Pagination,
Skeleton,
Typography
} from "@mui/material";
import Link from "next/link";
import { Link } from "react-router-dom";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
import {useTitle} from "@/app/hooks/useTitle";
@@ -32,7 +32,7 @@ export default function SubmissionInfoPage() {
setIsLoading(true);
try {
const res = await fetch(
`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`,
`/v1/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`,
{ signal: controller.signal }
);
@@ -55,24 +55,10 @@ export default function SubmissionInfoPage() {
return () => controller.abort();
}, [currentPage]);
if (isLoading || !submissions) {
return (
<Webpage>
<Container sx={{ height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Box display="flex" flexDirection="column" alignItems="center">
<CircularProgress />
<Typography variant="body1" sx={{ mt: 2 }}>
Loading submissions...
</Typography>
</Box>
</Container>
</Webpage>
);
}
const skeletonCards = Array.from({ length: cardsPerPage }, (_, i) => i);
const totalPages = submissions ? Math.ceil(submissions.Total / cardsPerPage) : 0;
const totalPages = Math.ceil(submissions.Total / cardsPerPage);
if (submissions.Total === 0) {
if (submissions && submissions.Total === 0) {
return (
<Webpage>
<Container sx={{ py: 6 }}>
@@ -86,14 +72,20 @@ export default function SubmissionInfoPage() {
return (
<Webpage>
<Container maxWidth="lg" sx={{ py: 6 }}>
<Box component="main" sx={{ width: '100%', px: 2 }}>
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
py: 6,
px: 2
}}>
<Box sx={{ width: '100%', maxWidth: '1200px' }}>
<Breadcrumbs
separator={<NavigateNextIcon fontSize="small" />}
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Typography color="text.secondary">Submissions</Typography>
@@ -111,26 +103,51 @@ export default function SubmissionInfoPage() {
className="grid"
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
gridTemplateColumns: {
xs: 'repeat(1, 1fr)',
sm: 'repeat(2, 1fr)',
md: 'repeat(3, 1fr)',
lg: 'repeat(4, 1fr)',
},
gap: 3,
width: '100%',
}}
>
{submissions.Submissions.map((submission) => (
<MapCard
key={submission.ID}
id={submission.ID}
assetId={submission.AssetID}
displayName={submission.DisplayName}
author={submission.Creator}
authorId={submission.Submitter}
rating={submission.StatusID}
statusID={submission.StatusID}
gameID={submission.GameID}
created={submission.CreatedAt}
type="submission"
/>
))}
{!submissions || isLoading ? (
skeletonCards.map((i) => (
<Card key={i} sx={{ height: '100%' }}>
<Skeleton variant="rectangular" height={180} />
<CardContent>
<Skeleton variant="text" width="80%" height={32} sx={{ mb: 1.5 }} />
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
<Skeleton variant="text" width={80} />
<Skeleton variant="text" width={100} />
</Box>
<Skeleton variant="text" width="60%" />
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 2 }}>
<Skeleton variant="circular" width={28} height={28} />
<Skeleton variant="text" width={100} />
</Box>
</CardContent>
</Card>
))
) : (
submissions.Submissions.map((submission) => (
<MapCard
key={submission.ID}
id={submission.ID}
assetId={submission.AssetID}
displayName={submission.DisplayName}
author={submission.Creator}
authorId={submission.Submitter}
rating={submission.StatusID}
statusID={submission.StatusID}
gameID={submission.GameID}
created={submission.CreatedAt}
type="submission"
/>
))
)}
</Box>
{totalPages > 1 && (
@@ -145,7 +162,7 @@ export default function SubmissionInfoPage() {
</Box>
)}
</Box>
</Container>
</Box>
</Webpage>
);
}

View File

@@ -1,5 +1,3 @@
"use client"
import React, { useState } from "react";
import {
Button,
@@ -17,7 +15,7 @@ import SendIcon from '@mui/icons-material/Send';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import Webpage from "@/app/_components/webpage";
import GameSelection from "./_game";
import Link from "next/link";
import { Link, useNavigate } from "react-router-dom";
import {useTitle} from "@/app/hooks/useTitle";
interface SubmissionPayload {
@@ -29,6 +27,7 @@ interface SubmissionPayload {
export default function SubmitPage() {
useTitle("Submit");
const navigate = useNavigate();
const [game, setGame] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -61,7 +60,7 @@ export default function SubmitPage() {
};
try {
const response = await fetch("/api/submissions", {
const response = await fetch("/v1/submissions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
@@ -73,7 +72,7 @@ export default function SubmitPage() {
}
const { OperationID } = await response.json();
window.location.assign(`/operations/${OperationID}`);
navigate(`/operations/${OperationID}`);
} catch (error) {
console.error("Error submitting data:", error);
setError(error instanceof Error ? error.message : "An unknown error occurred");
@@ -89,10 +88,10 @@ export default function SubmitPage() {
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link href="/" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Home</Typography>
</Link>
<Link href="/maps" passHref style={{ textDecoration: 'none', color: 'inherit' }}>
<Link to="/maps" style={{ textDecoration: 'none', color: 'inherit' }}>
<Typography color="text.primary">Maps</Typography>
</Link>
<Typography color="text.secondary">Submit Map</Typography>

View File

@@ -1,57 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { errorImageResponse } from "@/app/lib/errorImageResponse";
export async function GET(
request: NextRequest,
context: { params: Promise<{ assetId: string | undefined }> },
): Promise<NextResponse> {
const { assetId } = await context.params;
if (!assetId) {
return errorImageResponse(400, {
message: "Missing asset ID",
});
}
let finalAssetId = assetId;
try {
const mediaResponse = await fetch(
`https://publish.roblox.com/v1/assets/${assetId}/media`, // NOTE: This allows users to add custom images(their own thumbnail if they'd like) to their maps
);
if (mediaResponse.ok) {
const mediaData = await mediaResponse.json();
if (mediaData.data && mediaData.data.length > 0) {
finalAssetId = mediaData.data[0].toString();
}
}
} catch {}
try {
const response = await fetch(
`https://thumbnails.roblox.com/v1/assets?format=png&size=512x512&assetIds=${finalAssetId}`,
);
if (!response.ok) {
throw new Error(
`Failed to fetch thumbnail JSON [${response.status}]`,
);
}
const data = await response.json();
const imageUrl = data.data[0]?.imageUrl;
if (!imageUrl) {
return errorImageResponse(404, {
message: "No image URL found in the response",
});
}
// Redirect to the actual image URL instead of proxying
return NextResponse.redirect(imageUrl);
} catch (err) {
return errorImageResponse(500, {
message: `Failed to fetch thumbnail URL: ${err}`,
});
}
}

View File

@@ -1,20 +0,0 @@
import { NextRequest, NextResponse } from "next/server"
export async function GET(
request: NextRequest,
context: { params: Promise<{ mapId: string }> }
): Promise<NextResponse> {
// TODO: implement this, we need a cdn for in-game map thumbnails...
if (!process.env.API_HOST) {
throw new Error('env variable "API_HOST" is not set')
}
const { mapId } = await context.params
const apiHost = process.env.API_HOST.replace(/\/api\/?$/, "")
const redirectPath = `/thumbnails/asset/${mapId}`
const redirectUrl = `${apiHost}${redirectPath}`
return NextResponse.redirect(redirectUrl)
}

View File

@@ -1,43 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
export async function GET(
request: NextRequest,
context: { params: Promise<{ userId: string | undefined }> },
): Promise<NextResponse> {
const { userId } = await context.params; // Await params to access userId
if (!userId) {
return NextResponse.json(
{ error: "Missing userId parameter" },
{ status: 400 },
);
}
try {
const response = await fetch(
`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&size=420x420&format=Png&isCircular=false`,
);
if (!response.ok) {
throw new Error("Failed to fetch avatar headshot JSON");
}
const data = await response.json();
const imageUrl = data.data[0]?.imageUrl;
if (!imageUrl) {
return NextResponse.json(
{ error: "No image URL found in the response" },
{ status: 404 },
);
}
// Redirect to the image URL instead of proxying
return NextResponse.redirect(imageUrl);
} catch {
return NextResponse.json(
{ error: "Failed to fetch avatar headshot URL" },
{ status: 500 },
);
}
}

10
web/src/app/ts/Stats.ts Normal file
View File

@@ -0,0 +1,10 @@
interface Stats {
readonly TotalSubmissions: number;
readonly TotalMapfixes: number;
readonly ReleasedSubmissions: number;
readonly ReleasedMapfixes: number;
readonly SubmittedSubmissions: number;
readonly SubmittedMapfixes: number;
}
export type { Stats };

27
web/src/main.tsx Normal file
View File

@@ -0,0 +1,27 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
import './app/globals.scss'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
retry: 1,
},
},
})
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>,
)

View File

@@ -1,33 +0,0 @@
import { NextRequest, NextResponse } from "next/server"
export const config = {
matcher: ["/api/:path*", "/auth/:path*"],
}
export function middleware(request: NextRequest) {
const { pathname, search } = request.nextUrl
if (pathname.startsWith("/api")) {
if (!process.env.API_HOST) {
throw new Error('env variable "API_HOST" is not set')
}
const baseUrl = process.env.API_HOST.replace(/\/$/, "");
const path = pathname.replace(/^\/api/, "");
const apiUrl = new URL(baseUrl + path + search);
return NextResponse.rewrite(apiUrl, { request });
} else if (pathname.startsWith("/auth")) {
if (!process.env.AUTH_HOST) {
throw new Error('env variable "AUTH_HOST" is not set')
}
const authHost = process.env.AUTH_HOST.replace(/\/$/, "");
const path = pathname.replace(/^\/auth/, "");
const redirectUrl = new URL(authHost + path + search);
return NextResponse.redirect(redirectUrl, 302);
}
return NextResponse.next()
}

View File

@@ -1,28 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
"ES2020",
"DOM",
"DOM.Iterable"
],
"allowJs": true,
"module": "ESNext",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"noImplicitAny": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"esModuleInterop": true,
"allowJs": true,
"paths": {
"@/*": [
"./src/*"
@@ -30,14 +29,14 @@
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"next-env.d.ts",
"build/types/**/*.ts",
"build/dev/types/**/*.ts"
"src"
],
"exclude": [
"node_modules"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

Some files were not shown because too many files have changed in this diff Show More